REST API Design
REST (Representational State Transfer) is an architectural style for building APIs around resources and standard HTTP semantics. A good REST API is predictable, observable, secure, and evolvable — clients can guess how it behaves because it follows conventions.
Most REST design comes down to two habits: model your domain as resources, and use HTTP the way it was meant to be used. The rest is consistency.
TL;DR
- Model the domain as resources and collections (plural nouns).
- Use HTTP methods and status codes correctly and consistently.
- Return a stable error shape with a machine-readable code.
- Add pagination, filtering, and sorting from day one.
- Document with OpenAPI and treat it as the contract.
Quick Example
A create endpoint that uses the right method, status, and Location header, with a consistent response envelope:
Resource Modeling
Use plural nouns for collections and avoid verbs in paths:
/users,/projects,/projects/{projectId}/members- Prefer
PATCH /projects/{id}with{ "archived": true }over/archiveProject.
Use stable, public identifiers in URLs (/projects/proj_abc123) and avoid leaking internal database IDs.
HTTP Methods
GET must never change state. PUT replaces; PATCH applies a partial update (JSON Merge Patch or JSON Patch).
Status Codes
A practical working set:
200 OK— successful GET/PUT/PATCH201 Created— successful create (include aLocationheader)204 No Content— successful DELETE or empty-body update400 Bad Request— invalid input ·422— semantically invalid (optional)401 Unauthorized— not authenticated ·403 Forbidden— authenticated but not allowed404 Not Found— missing (or hidden) resource409 Conflict— version/uniqueness conflict ·429 Too Many Requests— rate limited500/503— server problems
Error Handling
Pick one error shape and use it everywhere so clients can handle failures reliably:
Keep code stable even if message changes, include a request_id, and never leak stack traces or secrets.
Pagination, Filtering, Sorting
Return pagination metadata (next_cursor, has_more), keep filters explicit (?status=active), and document allowed sort keys (?sort=-created_at).
Idempotency for Writes
For "create payment" or "create order," clients may retry. Accept an Idempotency-Key header and store the result keyed by (user, key) for a window, so retries don't create duplicates.
Versioning
Prefer evolvable changes (additive fields, new endpoints) that need no version bump. When you must version, URL versioning (/v1/projects) is the simplest and most explicit. See API Versioning.
Best Practices
- Auth: authenticate every request; authorize at the resource level, not just "is logged in." See API Security.
- Caching: use
ETag/If-None-Matchfor cacheable reads. See Caching. - Docs: maintain an OpenAPI spec as the contract.
- Observability: structured logs, request IDs, latency/error metrics.
Common Mistakes
Returning 200 for everything
Clients can't reason about failures if every response is 200.
Verbs in resource paths
FAQ
PUT or PATCH?
PUT replaces the entire resource (and is idempotent). PATCH applies a partial update — send only the fields that change. Use PATCH for typical "edit a couple of fields" operations.
When should I version my API?
Only when you make a breaking change you can't avoid. Additive changes (new optional fields, new endpoints) don't need a version bump. When you do version, be consistent — URL versioning is simplest.
Offset or cursor pagination?
Offset is fine for small, stable, admin-style lists. Use cursor pagination for feeds and large datasets where rows are inserted/deleted, since offsets drift and get slow.
401 or 403?
401 Unauthorized means "not authenticated" (no/invalid credentials). 403 Forbidden means "authenticated, but not allowed." Use 404 instead of 403 when you don't want to reveal that a resource exists.
Related Topics
- API Security — Securing endpoints
- Rate Limiting — Throttling and
429s - API Versioning — Evolving without breaking clients
- Error Handling — Consistent failure responses
- API Documentation — OpenAPI as a contract
- GraphQL — The main alternative style
- REST vs GraphQL — Choosing between the two API styles