REST API Design
REST is a set of conventions for building HTTP APIs that are predictable, cacheable, and easy to evolve. Done well, a REST API feels like a well-organized filing cabinet. Done badly, it is a maze.
What REST actually is (and is not)
REST stands for Representational State Transfer. Roy Fielding coined it in 2000. It is not a protocol, it is a set of architectural constraints that, when followed, give you APIs that are easy to consume, cache, and evolve. Most APIs that call themselves REST follow some of the rules. The fully strict version (HATEOAS, hypermedia) is rare in practice. What everyone means by REST today is "JSON over HTTP, organized around resources, using HTTP methods sensibly".
The core ideas
- Resources. Everything is a resource: a user, an order, a comment. Each has a URL.
- Standard methods. Use GET to read, POST to create, PUT/PATCH to update, DELETE to delete. Do not invent verbs in the URL.
- Stateless. Each request carries everything needed. Servers do not remember sessions between calls; they look them up from a token.
- JSON representations. Resources are returned as JSON (mostly).
- Cacheable. Responses signal whether and how long they can be cached.
Designing the URL structure
URLs name resources, not actions. Use nouns, plural, in lower case, with hyphens.
Good:
GET /users list users
GET /users/42 get user 42
POST /users create user
PATCH /users/42 update user 42
DELETE /users/42 delete user 42
GET /users/42/orders list orders for user 42
GET /orders/9911/items items in order 9911
Avoid:
POST /getUser
GET /createOrder?userId=42
POST /users/42/delete
Verbs in URLs make APIs feel like RPC and are harder to evolve. Stick to nouns and let the HTTP method express the action.
Versioning
APIs change. Clients out in the wild do not. You need a way to evolve without breaking them. Three common approaches:
- URL versioning:
/v1/users,/v2/users. Simplest. Easy to route. - Header versioning:
Accept: application/vnd.myapi.v2+json. Cleaner URLs but harder to test in a browser. - Date-based:
X-API-Version: 2024-06-01(Stripe style). Powerful but more work to maintain.
Pick one and stick to it. URL versioning is fine for most teams.
Pagination
Never return unbounded lists. They will grow until they break. Three pagination styles:
- Offset/limit:
?offset=100&limit=20. Simple, but bad on large datasets and unstable when items shift. - Cursor-based:
?cursor=eyJpZCI6Mzh9. Stable, scales well. The standard for high-traffic APIs. - Keyset:
?after_id=42&limit=20. Like cursor, simpler shape, requires a stable sort.
Filtering, sorting, and sparse fields
Common conventions:
?status=activefilter?sort=-created_atsort by created_at descending?fields=id,name,emailonly return certain fields?include=author,commentssideload related resources
These are not REST commandments, just common patterns. Whatever you choose, document it and stay consistent across endpoints.
Error responses that help
A 400 with no body is hostile. Always return a structured error.
{
"error": {
"code": "validation_failed",
"message": "Email is invalid",
"field": "email",
"trace_id": "abc-123"
}
}
Include a code (machine-readable), a message (human-readable), and a trace ID so support can find the request in logs.
HATEOAS, briefly
The strict REST original includes HATEOAS: each response embeds links to related resources, so the client navigates by following links rather than building URLs. Almost no production API does this fully. The exceptions (GitHub, PayPal partly) usually have very long-lived clients. For most teams, plain JSON with documented URLs is enough. Do not let HATEOAS guilt push you into over-engineering.
REST is mostly about discipline. Pick conventions, document them, apply them everywhere. The first ten endpoints set the precedent for the next thousand. Get them right.