Study interactive :: Progress tools open in the Study Hub reader.

08. API Paradigms

We've covered the plumbing (HTTP, WebSockets). Now the shape of the API itself. When you're designing a service, you pick a paradigm. Three big ones dominate today: REST, GraphQL, and gRPC.

There's no single right answer. Each one optimizes for different things.

REST

The default for most public web APIs. Built on top of HTTP. The idea: every "thing" in your system is a resource at a URL, and you act on it with HTTP verbs.

GET    /users         -> list users
GET    /users/42      -> get user 42
POST   /users         -> create user
PATCH  /users/42      -> update fields
DELETE /users/42      -> delete

That's 90% of what REST is in practice. The other 10% is the formal stuff (HATEOAS, hypermedia, statelessness) that almost nobody implements strictly. Most "REST" APIs are really "HTTP APIs with sensible URLs".

Good at: simple, cacheable, debuggable (curl, browser), works through every proxy and firewall.

Not so good at: under-fetching (need data from 3 endpoints to build one page), over-fetching (response has 50 fields, you needed 3), no schema enforcement out of the box.

A REST response:

GET /users/42

{
  "id": 42,
  "name": "Ada Lovelace",
  "email": "[email protected]",
  "created_at": "2026-01-15T10:23:00Z",
  "posts_count": 17
}

GraphQL

Built by Facebook around 2015. One endpoint. The client describes exactly what it wants.

query {
  user(id: 42) {
    name
    posts(last: 5) {
      title
      comments {
        author { name }
      }
    }
  }
}

The server returns exactly that shape. No more, no less.

Good at: clients with varying needs (web vs mobile vs partner integrations), avoiding round trips, strict type system.

Not so good at: caching (everything is POST to one URL, traditional HTTP caching doesn't work), authorization complexity (every field needs a check), exposing too much (an N+1 query problem can sneak in).

The standard server library is Apollo. The standard client lib is also Apollo. There's a whole ecosystem.

A tiny GraphQL example with Python + Strawberry:

import strawberry

@strawberry.type
class User:
    id: int
    name: str

@strawberry.type
class Query:
    @strawberry.field
    def user(self, id: int) -> User:
        return User(id=id, name="Ada")

schema = strawberry.Schema(query=Query)

gRPC

Google's RPC framework. Uses HTTP/2 under the hood, with messages serialized via Protocol Buffers (Protobuf). You define the contract in a .proto file and gRPC generates client and server stubs in many languages.

A .proto file:

syntax = "proto3";

service UserService {
  rpc GetUser (UserRequest) returns (User);
  rpc StreamUsers (Empty) returns (stream User);
}

message UserRequest {
  int32 id = 1;
}

message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
}

Run the codegen, and you get typed clients and servers in Go, Python, Java, etc. Calling looks like a normal function:

user = stub.GetUser(UserRequest(id=42))
print(user.name)

Good at: backend-to-backend microservice calls, streaming (HTTP/2), strong typing, small payloads (binary).

Not so good at: browsers (you need grpc-web, an extra proxy), debugging (binary payloads, can't curl), public APIs.

REST vs GraphQL vs gRPC: when to pick what

If you're building... Pick
A public API (Stripe, GitHub) REST
A complex frontend that needs flexible data GraphQL
Internal microservices, performance-critical gRPC
A mobile app on slow networks GraphQL or REST
A simple CRUD admin REST

Many companies use more than one. Stripe is REST for the public API and gRPC internally. GitHub offers both REST and GraphQL.

A few honorable mentions

Webhooks

The reverse of normal APIs. Instead of asking, the server calls you when something happens. Stripe uses webhooks heavily: "a payment succeeded", "a refund was issued".

POST https://yourapp.com/webhooks/stripe
{
  "type": "payment.succeeded",
  "data": { "amount": 1000, "currency": "usd" }
}

You expose an endpoint, give Stripe the URL, they POST to it. Authentication is usually a signed header.

Long polling and SSE

Mentioned in the WebSocket chapter. Still useful for one-way streams of events without the complexity of full WebSockets.

Webhooks vs polling vs WebSockets

A practical decision tree:

Versioning

Whatever paradigm you pick, you'll need to evolve it without breaking clients. Three common patterns:

1. URL versioning:    GET /v1/users/42
2. Header versioning: GET /users/42  with  Accept: application/vnd.app.v1+json
3. Query versioning:  GET /users/42?version=1

URL versioning is the most common because it's the easiest to debug. Big providers (Stripe, GitHub) use a different approach: date-based versions, with a per-account "default version" that doesn't change automatically. That way old integrations keep working forever.

Things to remember

Going deeper