Guide contents

Lesson 10 of 129

REST API Design

REST (Representational State Transfer) is an architectural style for networked systems described by Roy Fielding. In practice, a "REST API" is an HTTP API organized around resources: named things (a user, an order) that clients read and modify by exchanging representations of them, usually JSON, using standard HTTP methods.

Constraints of the REST style

  • Client-server: separates UI concerns from data storage.
  • Stateless: each request contains all information needed to process it. The server keeps no client session state between requests.
  • Cacheable: responses declare whether they can be cached.
  • Uniform interface: resources are identified by URLs, manipulated through representations, messages are self-descriptive, and (in the strict form) hypermedia drives state transitions.
  • Layered system: proxies, gateways, and caches can sit between client and server.

Statelessness has design consequences. Any server instance can handle any request, so servers scale horizontally behind a load balancer. Authentication data (a token) is sent with every request rather than stored in a server-side session tied to one machine. The cost is larger requests and repeated authentication work.

Resources and URLs

Model nouns, not actions. Use plural collection names and hierarchical paths for containment.

Purpose URL
Collection of orders /orders
One order /orders/1001
Items within an order /orders/1001/items
Filtered, sorted collection /orders?status=shipped&sort=-created_at

Guidelines:

  • Use nouns in paths (/orders), and let the HTTP method express the action.
  • Keep nesting shallow. Prefer /items/55 or /orders/1001/items over deep chains such as /customers/1/orders/2/items/3/parts/4.
  • Use query parameters for filtering, sorting, and pagination (see pagination, idempotency, and versioning).
  • Operations that do not map to CRUD can be modeled as sub-resources (POST /orders/1001/cancellation) or as an action endpoint (POST /orders/1001:cancel). Choose one convention and apply it consistently.

HTTP methods

Two properties define method semantics:

  • Safe: does not change server state. Safe methods can be prefetched and retried freely.
  • Idempotent: applying the same request several times has the same effect on server state as applying it once. Idempotent methods can be retried after a timeout without further protection.
Method Typical use Safe Idempotent
GET Read a resource or collection Yes Yes
HEAD Same as GET without body Yes Yes
POST Create a resource, or run a non-idempotent action No No
PUT Replace a resource at a known URL (full update) No Yes
PATCH Partial update No Not guaranteed by definition
DELETE Remove a resource No Yes

Notes:

  • PUT replaces the whole representation, so repeating it leaves the same state. Fields omitted from a PUT body are reset or removed.
  • PATCH sends only the changes. Formats include JSON Merge Patch (RFC 7396, a partial JSON object where null removes a field) and JSON Patch (RFC 6902, a list of operations such as add, replace, remove). A patch can be idempotent (set name = "x") or not (increment counter, or JSON Patch add to an array), depending on the format and content.
  • DELETE is idempotent in terms of state: after the first call the resource is gone, and later calls do not change anything further, although the response code may differ (404 instead of 204).
  • POST is not idempotent, so a retry after a timeout can create a duplicate. Idempotency keys address this.
  • GET must not change state, because caches, crawlers, and prefetchers issue GETs freely.

Status codes

Class Meaning Common codes
2xx Success 200 OK, 201 Created (with Location header), 202 Accepted (asynchronous processing), 204 No Content
3xx Redirection 301 permanent, 302/307 temporary, 304 Not Modified
4xx Client error 400 malformed request, 401 unauthenticated, 403 forbidden, 404 not found, 405 method not allowed, 409 conflict, 412 precondition failed, 415 unsupported media type, 422 semantic validation failure, 429 too many requests
5xx Server error 500 internal error, 502 bad gateway, 503 unavailable, 504 gateway timeout

Points of precision:

  • 401 means the credentials are missing or invalid. 403 means the caller is identified but not permitted.
  • Clients should treat 4xx as errors that retrying will not fix (except 429 and sometimes 409 or 408), and 5xx as potentially transient.
  • Return a structured error body with a machine-readable code and a human-readable message. The Problem Details format (RFC 9457, which replaced RFC 7807) standardizes this as application/problem+json with fields such as type, title, status, and detail.
  • A 405 response includes an Allow header listing the methods the resource supports.

Request flow example

sequenceDiagram
    participant C as Client
    participant S as API server
    C->>S: POST /orders (JSON body)
    S-->>C: 201 Created, Location: /orders/1001
    C->>S: GET /orders/1001
    S-->>C: 200 OK, ETag "v1", body
    C->>S: GET /orders/1001, If-None-Match "v1"
    S-->>C: 304 Not Modified

Caching headers

HTTP caching lets clients, proxies, and CDNs reuse responses.

  • Cache-Control: max-age=60: the response is fresh for 60 seconds. public allows shared caches to store it, private restricts it to the client's own cache, no-store forbids storing, and no-cache allows storing but requires revalidation before reuse.
  • Validators: ETag (an opaque version identifier) and Last-Modified. On a later request the client sends If-None-Match or If-Modified-Since. If unchanged, the server returns 304 Not Modified with no body, saving bandwidth.
  • Optimistic concurrency: an update sends If-Match: "<etag>". If the resource changed since, the server returns 412 Precondition Failed, preventing lost updates.
  • Vary lists request headers that affect the response (for example Accept-Encoding), so caches key entries correctly.

Only GET and HEAD responses are cached in practice. Responses that contain user-specific data should be private.

Representations and content negotiation

The client states what it accepts with Accept (application/json) and what it sends with Content-Type. Compression uses Accept-Encoding and Content-Encoding. JSON is the common format, and the schema of request and response bodies is described with a specification such as OpenAPI.

HATEOAS

HATEOAS (Hypermedia as the Engine of Application State) means responses include links describing the actions available next, so a client follows links instead of constructing URLs from documentation.

JSON
{
  "id": 1001,
  "status": "pending",
  "_links": {
    "self": { "href": "/orders/1001" },
    "cancel": { "href": "/orders/1001/cancellation" },
    "payment": { "href": "/orders/1001/payment" }
  }
}

It lets servers change URLs and expose only currently valid transitions (a shipped order has no cancel link). Most production JSON APIs skip it, and describe URLs in documentation, so they satisfy fewer REST constraints than the strict definition requires.

Design trade-offs

  • Over-fetching and under-fetching: fixed resource shapes can return more fields than needed or force several round trips. GraphQL and field selection parameters address this.
  • Chatty clients: many small calls raise latency. Composite endpoints or an API gateway can aggregate.
  • Versioning and evolution: adding optional fields is compatible; removing or changing meaning is not.
  • Internal RPC: for high-volume service-to-service calls, gRPC offers a binary format and generated clients.

Interview questions

  • What is the difference between PUT and POST? PUT targets a known URL and replaces the resource, and is idempotent. POST usually targets a collection, lets the server assign the identifier, and is not idempotent.
  • What does stateless mean, and why does it help scaling? The server keeps no per-client state between requests, so any instance can serve any request, and instances can be added or removed freely behind a load balancer.
  • How do you prevent lost updates in a REST API? Return an ETag with the resource and require If-Match on updates. A mismatch yields 412, so the client refetches and retries.
  • When is 401 correct versus 403? 401 for missing or invalid authentication, 403 when the authenticated caller lacks permission.