Intuition

An API is a promise: “Send me this shape of request, and I will return that shape of response.” The quality of an API is measured not by its cleverness but by how rarely its consumers need to read the documentation twice. Good APIs are predictable, consistent, and hard to misuse.

The paradigm - REST, GraphQL, RPC - matters less than the discipline of thinking carefully about what you expose, what you hide, and what contracts the type system can enforce for you.


Core Idea

REST (Representational State Transfer)

Resources are identified by URLs. Operations map to HTTP methods. State is transferred as representations (typically JSON).

MethodSemanticsIdempotent?
GETRead a resourceYes
POSTCreate a new resourceNo
PUTReplace a resource entirelyYes
PATCHPartially update a resourceDepends
DELETERemove a resourceYes

Key principles:

  • Resources are nouns (/users/42), not verbs (/getUser?id=42).
  • Use HTTP status codes meaningfully (201 Created, 404 Not Found, 409 Conflict).
  • Hypermedia (HATEOAS) lets responses contain links to related actions, though few APIs achieve this fully.

GraphQL

A query language where the client specifies exactly which fields it needs. A single endpoint serves all queries.

query {
  user(id: 42) {
    name
    posts(limit: 5) {
      title
      createdAt
    }
  }
}
  • Strengths: eliminates over-fetching and under-fetching; strongly typed schema; introspectable.
  • Trade-offs: complexity shifts to the server (query parsing, authorization per field, N+1 problems); caching is harder than REST.

RPC (Remote Procedure Call)

Expose server functions directly. The client calls a named procedure with arguments and receives a result. gRPC (Protocol Buffers over HTTP/2) is the dominant modern implementation.

  • Strengths: efficient binary serialization; strong typing via schema; streaming support; code generation.
  • Trade-offs: less discoverable than REST; tighter coupling between client and server versions.

Contract Design Principles

Regardless of paradigm, well-designed APIs share common traits:

  1. Consistency - similar operations behave the same way everywhere.
  2. Least surprise - naming, error formats, and conventions match what developers expect.
  3. Evolvability - additive changes (new fields, new endpoints) don’t break existing clients.
  4. Explicit errors - error responses carry enough detail to diagnose without server logs.
  5. Versioning strategy - URL path (/v2/users), header (Accept: application/vnd.api+json;v=2), or schema evolution (Protocol Buffers’ field numbering).

Note

The strongest API contracts are enforced by type systems. A .proto file or GraphQL schema catches breaking changes at compile time - far cheaper than discovering them in production. See Type Systems - Goals & Guarantees for why static contracts matter.


Example

The same operation - “get a user’s recent posts” - across paradigms:

REST:

GET /users/42/posts?limit=5&sort=created_at:desc
Accept: application/json

GraphQL:

query {
  user(id: 42) {
    posts(limit: 5, sort: CREATED_AT_DESC) {
      id
      title
    }
  }
}

gRPC:

rpc GetUserPosts(GetUserPostsRequest) returns (PostList);
message GetUserPostsRequest {
  int32 user_id = 1;
  int32 limit = 2;
}

REST returns a fixed shape. GraphQL returns exactly what was asked. gRPC returns a typed message. The choice depends on client diversity, performance needs, and team familiarity.