REST API Design — Detailed#
Resource model#
/orders collection
/orders/{id} one order
/orders/{id}/items sub-collection
/orders/{id}/items/{lid} one item
/customers/{id}/orders nested view of a customer's orders
Nouns, plural. URLs are stable; verbs are HTTP.
Verb cheat sheet#
| Verb | Idempotent | Safe | Used for |
|---|---|---|---|
| GET | yes | yes | read |
| HEAD | yes | yes | metadata only |
| OPTIONS | yes | yes | CORS / capabilities |
| PUT | yes | no | replace whole resource |
| DELETE | yes | no | remove |
| POST | no | no | create / non-idempotent action |
| PATCH | not by default | no | partial update |
"Idempotent" = same effect if repeated → safe to retry on network errors.
Status codes you'll actually use#
| Code | Meaning |
|---|---|
| 200 OK | read / non-creating success |
| 201 Created | new resource; Location: header points at it |
| 202 Accepted | async work queued |
| 204 No Content | success with empty body (DELETE) |
| 400 Bad Request | validation / parse error |
| 401 Unauthorized | missing or invalid auth |
| 403 Forbidden | authenticated but not allowed |
| 404 Not Found | resource doesn't exist |
| 405 Method Not Allowed | wrong verb for this URL |
| 409 Conflict | optimistic concurrency / business conflict |
| 410 Gone | sunset version |
| 422 Unprocessable | well-formed but semantically invalid |
| 429 Too Many Requests | rate limited; Retry-After |
| 500 Internal Server Error | bug |
| 502/503/504 | gateway / upstream / timeout |
Error envelope (RFC 7807 Problem Details)#
{
"type": "https://errors.example.com/insufficient_funds",
"title": "Insufficient funds",
"status": 422,
"code": "INSUFFICIENT_FUNDS",
"detail": "Account 42 has balance 5, requested withdrawal 100",
"instance": "/withdrawals/abc-123",
"trace_id": "0a8b...",
"errors": [
{"field": "amount", "code": "TOO_LARGE"}
]
}
Stable code for programmatic handling; trace_id so support can find the log line.
Idempotency#
sequenceDiagram
participant C as Client
participant S as Server
participant DB as Idempotency table
C->>S: POST /charges + Idempotency-Key: abc
S->>DB: lookup abc
alt new
DB-->>S: not found
S->>S: do work
S->>DB: store (abc, response, 200)
S-->>C: 200 + result
else seen
DB-->>S: cached response
S-->>C: same response
end
Client supplies Idempotency-Key header. Server stores (key → response, status) for 24-48 h. See idempotency-retries.
Pagination#
| Style | Pros | Cons |
|---|---|---|
Offset (?offset=200&limit=50) |
trivial | skips/duplicates when data changes |
Page-based (?page=4) |
same as offset, prettier | same drift |
Cursor (?cursor=opaque) |
stable, scales | random access hard |
Time-based (?since=ts) |
natural for events | gaps if events share ts |
Use cursor by default; next_cursor in the response.
Filtering / sorting / sparse fieldsets#
Keep filters typed (status=PAID not status="paid"). Sort prefixes: - desc.
Search vs lookup#
- Lookup by id:
/orders/{id}→ 404 if absent. - Search:
/orders?status=...&q=...→ 200 with possibly empty list.
Bulk actions#
POST /orders/bulkwith array.- Return per-item result with HTTP 200 +
errors[](don't fail the whole batch on one bad row).
Long-running operations#
POST /reports → 202 Accepted, Location: /operations/op_1
GET /operations/op_1 → status: running | done | failed
GET /operations/op_1/result → final payload when done
Caching headers#
ETagfor conditional GET (If-None-Match).Last-Modified/If-Modified-Since.Cache-Control: public, max-age=...,s-maxage=...for shared caches.
Authn / Authz#
- Authentication: Bearer JWT or session cookie.
- Authorization: per-resource checks (404 vs 403 — 404 leaks less).
- mTLS for B2B.
Versioning#
Anti-patterns#
- Verbs in URLs (
/getOrder?id=...). - Returning 200 with
{ "error": "..." }— use proper status. - Mixing snake_case + camelCase in the same payload.
nullfor "field unchanged" in PATCH — use JSON Merge Patch or JSON Patch standard.- Exposing primary keys without context — prefix with type (
ord_abc).
Richardson Maturity Model#
- L0: tunnels everything over POST.
- L1: resources at URLs.
- L2: HTTP verbs + statuses (most APIs).
- L3: HATEOAS — hypermedia links in responses (rare in practice).
L2 is the realistic target.
Glossary & fundamentals#
Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.
| Tag | Concept | What it is | Page |
|---|---|---|---|
HLD |
LSM vs B-Tree engines | WAL, memtable, SSTables, compaction | storage-engines-lsm-btree |
HLD |
Idempotency & retries | safe re-execution, backoff + jitter | idempotency-retries |
HLD |
Service mesh | sidecar mesh, mTLS, traffic policy | service-mesh |
HLD |
API versioning & evolution | URI / header versioning, sunsetting | api-versioning-evolution |
LLD |
REST API design | verbs, statuses, pagination, errors | rest-api-design |