Backend / REST APIs / 05_rest_principles.md

REST principles

Updated 5 min read source
On this page9
  1. The six constraints
  2. The uniform interface — four sub-constraints
  3. Resources and URI design
  4. Methods and their contracts
  5. Representations and content negotiation
  6. What “RESTful enough” means in practice
  7. Common pitfalls
  8. Common interview confusions
  9. Interview angle

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 Stateful vs Stateless (REST and beyond); caching in REST Error Handling and HTTP Caching.

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 (HTTP Semantics and Caching).
  4. HATEOAS — responses link to available next actions. The most-cited, least-implemented constraint; see Richardson Maturity Model and HATEOAS 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.

The action-as-resource move is worth seeing, because the cancellation then has its own identity — a timestamp, a reason, an author — which a magic field on the order cannot carry:

python
@router.post("/orders/{id}/cancellation", status_code=201)
async def cancel(id: str, body: CancellationIn) -> Cancellation:
    ...

# vs the alternative, which loses all of that:
# PATCH /orders/42  {"status": "cancelled"}

It also gives you somewhere to GET. “Why was this cancelled and by whom” is a question the PATCH version answers only from an audit log.

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: PUT vs PATCH: Understanding the Difference, Idempotency (including idempotency keys for POST). Status-code discipline: Status codes.

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:

http
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 REST Versioning and Pagination.

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 — REST Error Handling and HTTP Caching).
  6. Pagination, filtering, versioning conventions (REST Versioning and Pagination).

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 (API Protocols Comparison — REST, GraphQL, gRPC, SOAP).
  • 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 4

  • “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 (Richardson Maturity Model and HATEOAS).