REST principles
REST (Representational State Transfer) is an architectural style, not a protocol or a spec — Roy Fielding’s 2000 dissertation describing the constraints that made the web scale. An API is “RESTful” to the degree it honors those constraints; in practice most “REST APIs” are pragmatic HTTP+JSON APIs that satisfy the important ones.
The six constraints
| Constraint | Meaning | What it buys |
|---|---|---|
| Client–server | UI concerns separated from data storage | independent evolution of both sides |
| Stateless | every request self-contained; no server session | horizontal scaling, any replica serves any request |
| Cacheable | responses declare their own cacheability | fewer round-trips, CDN offload |
| Uniform interface | one generic way to interact with any resource | decoupling; the heart of REST (below) |
| Layered system | client can’t tell if it talks to the origin or a proxy | LBs, gateways, CDNs insertable at will |
| Code on demand | (optional) server ships executable code (JS) | rarely relevant to backend APIs |
Statelessness is covered in depth in 09_stateful_vs_stateless.md; caching in 08_error_handling_caching.md.
The uniform interface — four sub-constraints
- Identification of resources — every thing has a URI:
/users/123,/orders/42/items. - Manipulation through representations — you never touch the resource itself, only representations of it (a JSON document you GET, modify, and PUT back).
- Self-descriptive messages — each message carries enough to process it: method,
Content-Type, cache headers (../12_protocols/http/03_http_semantics_and_caching.md). - HATEOAS — responses link to available next actions. The most-cited, least-implemented constraint; see 07_richardson_maturity_hateoas.md for why level 2 is the industry plateau.
Resources and URI design
Resources are nouns; the methods are the verbs.
| Good | Bad | Why |
|---|---|---|
GET /users/123 |
GET /getUser?id=123 |
verb belongs in the method |
POST /orders |
POST /createOrder |
collection + POST = create |
GET /users/123/orders?status=open |
GET /users/123/openOrders |
filters are query params, not new resources |
POST /orders/42/cancellation |
POST /cancelOrder?id=42 |
actions modeled as sub-resources |
Conventions that hold up:
- Collections plural (
/users), items by id (/users/123). - Nest one level for ownership (
/users/123/orders); deeper nesting (/users/123/orders/42/items/7) is brittle — items usually deserve a top-level URI once they have their own identity. - Actions that don’t map to CRUD (cancel, approve, retry): model the action as a resource (
POST /orders/42/cancellation) or accept a pragmatic verb sub-path (POST /orders/42/cancel). Both beat tunneling everything throughPATCHwith magic fields.
Methods and their contracts
The method grid — safety and idempotency are contracts you must uphold server-side, not descriptions that come true automatically:
| Method | Use | Safe | Idempotent |
|---|---|---|---|
| GET | read | yes | yes |
| POST | create / non-idempotent action | no | no |
| PUT | full replace at a known URI | no | yes |
| PATCH | partial update | no | no (can be designed to be) |
| DELETE | remove | no | yes |
Details and gotchas: 04_put_vs_patch.md, 01_idempotency.md (including idempotency keys for POST). Status-code discipline: 03_status_codes.md.
Representations and content negotiation
A resource is not its JSON. The client asks for a representation via Accept, the server labels what it returns via Content-Type:
GET /users/123 HTTP/1.1
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id": 123, "name": "Ada", "links": {"orders": "/users/123/orders"}}
REST does not mandate JSON — that’s convention. Versioning via media types (Accept: application/vnd.api.v2+json) vs URL is covered in 06_versioning_pagination.md.
What “RESTful enough” means in practice
The pragmatic checklist most teams (and interviewers) actually mean:
- Nouns for URIs, methods for verbs.
- Correct status codes — not 200-with-
{"error": ...}. - Stateless requests (auth via token per request).
- GET is safe and cacheable; PUT/DELETE are idempotent.
- Consistent error shape (RFC 7807 — 08_error_handling_caching.md).
- Pagination, filtering, versioning conventions (06_versioning_pagination.md).
HATEOAS is where most stop — that’s Richardson level 2, and it’s fine. Know why you’re not doing level 3, not just that you aren’t.
Common pitfalls
- Verbs in URLs (
/api/getUsers) — RPC in REST clothing. If that’s what the domain wants, consider actual RPC (../12_protocols/00_api_protocols_comparison.md). - 200 for everything, errors described only in the body — breaks caches, retries, monitoring, and every generic HTTP client.
- Chatty resources — forcing N+1 GETs for one screen. Compose (
?include=), aggregate endpoints, or a BFF; don’t pretend the constraint doesn’t exist. - PUT that partially updates — violates the replace contract; that’s PATCH’s job.
- Session state on the server (“the previous request selected the account”) — breaks statelessness and horizontal scaling.
Common interview confusions
- REST ≠ HTTP. REST is the style; HTTP is the protocol it’s usually expressed in. You can violate REST over HTTP (most RPC-ish APIs do) — and theoretically apply REST elsewhere.
- REST ≠ JSON. Representation format is negotiable.
- “RESTful” ≠ “has HATEOAS”. Fielding would say yes; industry means level 2. Know both readings.
Interview angle
- “What are the main REST principles?” — Constraints first (stateless, cacheable, uniform interface), then the pragmatic checklist. Naming “uniform interface” and its sub-constraints separates senior answers from listicle answers.
- “Design the URLs for orders with a cancel action.” — Collections/nouns, then show the action-as-subresource move and say why not
GET /cancelOrder. - “Why must GET be safe?” — Caches, prefetchers, and crawlers assume it; a state-changing GET gets replayed by infrastructure you don’t control.
- “Is your API truly RESTful without HATEOAS?” — Richardson levels; defend level 2 as a deliberate trade-off (07_richardson_maturity_hateoas.md).