REST APIs
REST is the closest thing the web has to a default. When someone says “we’ll expose an API,” they almost always mean a REST-ish HTTP API unless they say otherwise. To understand why it won, you have to see it not as a technology but as a set of constraints that happen to align perfectly with how the web already works. REST didn’t add machinery to the web; it described the web’s own grain and said go with it.
The core idea: resources and a fixed set of verbs
Section titled “The core idea: resources and a fixed set of verbs”A traditional API exposes actions: createUser(), deactivateUser(), getUserOrders(). Each
new capability is a new verb, and the vocabulary grows without bound. REST inverts this. It says:
model your domain as nouns — resources — each with a stable address (a URL), and then act on
them with a small, fixed set of verbs that HTTP already defines.
Action-oriented (RPC-ish) Resource-oriented (REST) POST /createUser POST /users POST /getUser?id=42 GET /users/42 POST /updateUserEmail PATCH /users/42 POST /deleteUser DELETE /users/42 POST /getUserOrders GET /users/42/ordersThe verbs never grow; only the noun-space does. This is why REST APIs feel discoverable: once you know the four or five verbs, you can guess most endpoints.
| Verb | Meaning | Safe? | Idempotent? |
|---|---|---|---|
GET | read a resource | yes | yes |
POST | create / non-idempotent | no | no |
PUT | replace a resource | no | yes |
PATCH | partially update | no | no* |
DELETE | remove a resource | no | yes |
*PATCH can be made idempotent depending on how you define the patch.
Idempotency: the property that survives an unreliable network
Section titled “Idempotency: the property that survives an unreliable network”Safe means “doesn’t change state” (a GET can be repeated freely). Idempotent means “doing
it N times has the same effect as doing it once.” This sounds academic until you remember the
fallacies of distributed computing: the network
will drop responses. A client sends DELETE /users/42, the server deletes the user, and the
response gets lost. The client retries. Because DELETE is idempotent, the second call is
harmless — the user is already gone, you return the same result.
Now consider POST /payments. It is not idempotent. A lost response and a blind retry could
charge the customer twice. This is the single most important practical lesson of REST design:
Statelessness: each request carries its own context
Section titled “Statelessness: each request carries its own context”REST is stateless: the server keeps no per-client conversational memory between requests. Every request must carry everything needed to serve it — auth token, parameters, body. The server doesn’t remember that “this is the same client who logged in two requests ago” unless that fact travels in the request (e.g., in a token).
What does this buy us, and what does it cost? It buys horizontal scalability: because no server holds client-specific state, any replica can handle any request, so a load balancer can spray traffic across a fleet freely (see statelessness & sessions). It costs redundancy on the wire — every request re-sends auth and context — and it pushes session state somewhere else (a token, a cache, a database).
Status codes: a shared vocabulary for outcomes
Section titled “Status codes: a shared vocabulary for outcomes”HTTP gives you a standard way to say what happened, grouped by leading digit:
2xx success 200 OK, 201 Created, 204 No Content3xx redirection 301 Moved Permanently, 304 Not Modified4xx client's fault 400 Bad Request, 401 Unauthorized, 404 Not Found, 409 Conflict5xx server's fault 500 Internal Error, 503 Service UnavailableThe discipline that separates a good API from a frustrating one: 4xx means the caller must change
something; 5xx means the caller may retry later. Returning 200 OK with an error message buried in
the body breaks every monitoring tool and retry policy that relies on the status code. Be honest with
the number.
Versioning: evolving without breaking callers
Section titled “Versioning: evolving without breaking callers”Once clients depend on your API, you can’t freely change it — a renamed field breaks them. So you version. Two common styles:
- URL versioning:
/v1/users,/v2/users. Crude but unmissable and easy to route. - Header versioning:
Accept: application/vnd.myapp.v2+json. Cleaner URLs, but invisible and easier to get wrong.
The honest trade-off: versioning buys you the freedom to evolve, at the cost of maintaining multiple versions in parallel — every version you ship is one you must keep alive until its last caller leaves.
Pagination: never return an unbounded list
Section titled “Pagination: never return an unbounded list”GET /events on a table with ten million rows is a denial-of-service vector against yourself. You
must paginate. Two families:
- Offset/limit (
?offset=40&limit=20): simple, lets you jump to any page — but slow on deep pages (the database still scans past skipped rows) and unstable if rows are inserted while paging. - Cursor/keyset (
?after=<opaque_cursor>): you pass back a pointer to the last item seen. Fast and stable under inserts, but you can’t jump to “page 500” directly.
Cursor pagination wins for large, mutating datasets precisely because it leans on an index instead of counting from the start.
Where REST strains
Section titled “Where REST strains”REST is the right default, but it is not free of friction:
- Over- and under-fetching. A fixed resource shape rarely matches every screen. A mobile view wants three fields; the endpoint returns thirty. Or it needs data from three resources and must make three round trips. This exact pain is what GraphQL attacks.
- Chattiness. Resource-orientation can mean many small calls where one custom call would do.
- Weak typing across the wire. JSON has no enforced schema by default; the contract lives in documentation, not in the compiler — which is part of the appeal of RPC/gRPC for internal services.
The thread
Section titled “The thread”How do two services agree on what an operation means? REST answers: agree on a small, universal set of verbs and let the nouns carry the meaning. By riding HTTP’s existing semantics — methods, status codes, statelessness — REST gets caching, scalability, and tooling almost for free. You pay in rigidity of shape and chattiness, which is exactly why the next chapters exist.
Check your understanding
Section titled “Check your understanding”- Why does a resource-oriented design keep the verb vocabulary small while an action-oriented one grows it without bound?
- Distinguish safe from idempotent, and explain why idempotency matters specifically because the network is unreliable.
- How does an idempotency key make a
POST /paymentssafe to retry? - Why does statelessness enable horizontal scaling, and what cost does it impose?
- When would you choose cursor pagination over offset pagination, and what capability do you give up by doing so?