backend / rest apis / 05_rest_principles.md

REST principles

4 min read source

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

  1. Identification of resources — every thing has a URI: /users/123, /orders/42/items.
  2. Manipulation through representations — you never touch the resource itself, only representations of it (a JSON document you GET, modify, and PUT back).
  3. Self-descriptive messages — each message carries enough to process it: method, Content-Type, cache headers (../12_protocols/http/03_http_semantics_and_caching.md).
  4. 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 through PATCH with 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:

  1. Nouns for URIs, methods for verbs.
  2. Correct status codes — not 200-with-{"error": ...}.
  3. Stateless requests (auth via token per request).
  4. GET is safe and cacheable; PUT/DELETE are idempotent.
  5. Consistent error shape (RFC 7807 — 08_error_handling_caching.md).
  6. 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).