Module 04 · API fundamentals · Lesson 3 of 15
REST API design
Resource naming, methods and status codes used as intended, consistent error bodies, and the handful of conventions that make a REST API predictable. Includes the mistakes that mark an API as home-grown.
Why it matters
A REST API that follows conventions can be learned in ten minutes by anyone who has used another one; an API that invents its own needs documentation for every endpoint and still surprises people. Conventions also let infrastructure help you: caches cache GETs, retries retry idempotent methods, monitoring groups by status class. Getting REST right is less about theory than about not undermining the tools that already understand HTTP.
The idea
REST (as practised) means: resources identified by URLs, HTTP methods for operations, status codes for outcomes, and representations (usually JSON) for state. The conventions below are what "RESTful" means in a code review.
Resources and URLs
Nouns, plural, hierarchical where ownership is real: /customers/{id}/orders/{orderId}. Avoid verbs in paths; the method is the verb. When an action does not fit CRUD (ship an order, refund a payment), model it as a sub-resource or a state change: POST /orders/{id}/shipments or PATCH /orders/{id} with status: shipped. Keep nesting to two levels; deeper paths are hard to use and usually mean the child deserves its own top-level collection.
Methods and status codes
| Operation | Request | Success | Common errors |
|---|---|---|---|
| List | GET /orders?status=open&limit=50 | 200 with an array and paging info | 400 bad filter, 401, 403 |
| Read | GET /orders/42 | 200 with the resource | 404 not found |
| Create | POST /orders (+ Idempotency-Key) | 201 with Location header and the resource | 400 malformed, 422 invalid, 409 duplicate key |
| Replace | PUT /orders/42 | 200, or 204 with no body | 404, 409 version conflict |
| Partial update | PATCH /orders/42 | 200 with the resource | 422 invalid field, 409 conflict |
| Delete | DELETE /orders/42 | 204 (idempotent: a repeat is 204 or 404) | 404, 409 if not deletable in this state |
Errors have one shape
Pick a single error envelope and use it everywhere: a stable machine-readable code, a human message, optional details with field paths, and a request id for support. RFC 9457 (problem details) is a ready-made standard. Clients branch on code; they never parse message.
Concurrency and caching
Return an ETag on reads and require If-Match on updates so two clients editing the same resource cannot silently overwrite each other (optimistic concurrency, 412 on mismatch). Set Cache-Control on GETs honestly: private, max-age=0 for per-user data, longer for public reference data. Support If-None-Match so clients revalidate cheaply with 304.
Conventions that prevent surprises
- Consistent casing (pick
snake_caseorcamelCaseand never mix), ISO 8601 timestamps in UTC, money as integer minor units plus a currency code, ids as opaque strings. - Filtering, sorting and pagination as query parameters with documented defaults and maximums (next lesson).
- Partial responses or field selection (
?fields=) when payloads are large; otherwise accept that REST over-fetches and move on. - A machine-readable spec (OpenAPI) that is the source of truth for docs, client generation and contract tests.
- Hypermedia links (HATEOAS) are optional in practice; a
Locationheader on create andnextlinks on lists cover most real needs.
Trade-offs
Strict REST is predictable and tool-friendly and can feel verbose for action-heavy domains, where you end up modelling verbs as resources. Field selection and embedding reduce round trips but complicate caching and the server. Optimistic concurrency protects data and adds a header clients must learn to send. Each convention costs a little discipline and saves a lot of support.
In an interview
Design one endpoint fully rather than ten sketchily: "POST /orders with an Idempotency-Key, body validated against the schema, 201 with Location on success, 409 if the key was seen with a different body, 422 for business-rule failures with a code the client can act on. GET /orders/42 returns an ETag; PATCH requires If-Match." That demonstrates every convention in thirty seconds.
Check yourself
An API returns HTTP 200 for every request and puts {'success': false, 'error': '...'} in the body when something goes wrong. What does this break?