Skip to content
yutils

How REST Actually Works

Roy Fielding's actual REST — resources, representations, statelessness, the uniform interface, Richardson Maturity levels 0-3, HATEOAS, and why most APIs called 'REST' are really just JSON over HTTP.

~9 min read

"REST API" shows up in nearly every backend interview, doc, and blog post. But ask "what is REST" and answers diverge: "HTTP + JSON", "nouns in URLs", "uses GET/POST correctly". All three are close to wrong. REST is an architectural style defined in Roy Fielding's 2000 dissertation, and 90% of what we call "REST APIs" today is not really REST by his definition. This guide covers the actual REST constraints, common misconceptions, the Richardson Maturity Model, HATEOAS, and what people really mean when they say "REST" in practice.

Fielding's REST — Six Constraints

REST is not a protocol or a spec — it's an architectural style for distributed hypermedia systems. Satisfy all six constraints and it's "RESTful".

1. Client-Server      — separation of concerns (UI vs storage)
2. Stateless          — every request carries everything it needs; no server session
3. Cacheable          — responses say whether they're cacheable
4. Uniform Interface  — standardized interface (this is what makes REST REST)
5. Layered System     — works through proxies / gateways / load balancers
6. Code on Demand     — (optional) server may ship code to client (JS etc.)

The "Uniform Interface" is what distinguishes REST.
The rest are essentially built into HTTP already.

Uniform Interface — REST's Four Pillars

1. Resource Identification (URI)
   /users/42 → "the user resource with id 42"
   URL identifies the resource, not an action.

2. Representation
   You exchange representations of the resource, not the resource itself.
   The same user can be JSON, XML, or HTML.
   Content-Type / Accept headers negotiate.

3. Self-descriptive Messages
   Requests and responses describe themselves — Content-Type, Cache-Control,
   status codes, and other standard metadata.

4. HATEOAS (Hypermedia As The Engine Of Application State)
   Responses include links to the next possible actions.
   Clients navigate by following links — no need to know URL structures
   in advance.

Resource · Method · Representation — The Three Axes

# Resource
URLs are nouns (resource identifiers), not verbs
GOOD: /users/42, /orders/77/items
BAD:  /getUser?id=42, /createOrder

# Method (HTTP verb is the action)
GET     /users/42  → fetch (safe, idempotent, cacheable)
POST    /users     → create (not idempotent)
PUT     /users/42  → replace whole (idempotent)
PATCH   /users/42  → partial update (idempotency by definition)
DELETE  /users/42  → delete (idempotent)

# Representation
The same /users/42 in multiple representations:
  Accept: application/json   → {"id":42,"name":"jade"}
  Accept: application/xml    → <user id="42">...</user>
  Accept: text/html          → <h1>jade</h1>

→ URL doesn't change; only the representation is negotiated.

For the deeper meaning of idempotency see idempotency-keys; for picking the right status code see http-status-codes.

Richardson Maturity Model — REST in Four Levels

Proposed by Leonard Richardson. Classifies how close an API is to REST.

Level 0 — "The Swamp of POX" (Plain Old XML)
  POST /api  → all requests one endpoint, action inside the body
  Effectively SOAP / RPC over HTTP. URL and method are meaningless.
  e.g. { "action": "getUser", "id": 42 }

Level 1 — Resources
  /users/42, /orders/77 — URLs identify resources.
  But the method is still POST only (or just GET/POST).

Level 2 — HTTP Verbs
  GET /users/42, POST /users, DELETE /users/42 — uses standard methods.
  Status codes 200/201/404/409 used meaningfully.
  → Most of what we call "REST APIs" stops here.

Level 3 — HATEOAS (Hypermedia Controls)
  Responses include links to next possible actions.
  Clients don't hardcode URLs — they navigate via responses.
  → The actual Fielding REST. Almost never seen in practice.

HATEOAS — The Most Unused Part

Level 3 example — links to next actions in an order response:

GET /orders/77
{
  "id": 77,
  "status": "pending_payment",
  "total": 50000,
  "_links": {
    "self":    { "href": "/orders/77" },
    "pay":     { "href": "/orders/77/payment", "method": "POST" },
    "cancel":  { "href": "/orders/77", "method": "DELETE" }
  }
}

Client just follows the "pay" link → no prior knowledge of URL structure.

Pros:
- Resilient to API evolution — URLs change, only links update
- Discoverability — the response tells you what comes next

Why isn't it used?
- Clients (especially SPAs) hardcode URLs (perf / UX)
- Generic hypermedia clients basically don't exist (outside browsers)
- Response payload size ↑, parse complexity ↑
- Most mobile/web clients have a pre-baked flow → following links is moot

→ Fielding himself wrote (2008) that he was "frustrated with people
  calling APIs REST that aren't", but the industry stuck at Level 2.

Stateless — The Most Frequently Violated Constraint

Stateless = the server may NOT remember prior requests from the client
          = every request carries all information needed

GOOD:
  GET /orders?page=3
  Authorization: Bearer <jwt>
  → all info (auth, page) is in the request

BAD (server-side session):
  GET /next-page
  Cookie: sessionid=abc123
  → server has to remember "what was abc123's last page"

Pros:
- Easy horizontal scaling (any server can handle any request)
- Better cache effectiveness
- Simple retries on failure

Pitfalls:
- Cost of sending an auth token per request (vs one session cookie)
- JWT invalidation is hard (post-issuance permission changes)

→ Auth flows are compatible with stateless, but setup needs care.
  See the oauth2-explained guide.

"Not REST but Called REST" — Common Examples

# 1. Verb in the URL
POST /users/42/setActive   ← action is in the URL (violates REST)
Prefer: PATCH /users/42 { "active": true }

# 2. Ignoring status codes
HTTP 200 OK
{ "success": false, "error": "not found" }   ← really a 200?
Prefer: HTTP 404 + details in body

# 3. Misusing methods
GET /users/42/delete    ← GET with side effects (violates "safe")
Prefer: DELETE /users/42

# 4. No links in responses (Level 2 ceiling)
→ Technically not RESTful but the de facto standard.

→ In other words, 90% of "REST APIs" are Level 2 — HTTP + JSON +
  noun URLs. Not bad — just know the difference.

Common Misconceptions

  • "REST = HTTP" — REST is protocol-agnostic. In theory it can run over AMQP / WebSocket (in practice it's ~100% HTTP). HTTP's features (cache, status codes, methods) just happen to match REST very well.
  • "REST = JSON" — representation is negotiable. XML, MessagePack, Protobuf, HTML are all valid.
  • "PUT is update, POST is create" — PUT actually means an idempotent full replacement (put this representation at this URI). PUT can also create if the client picks the ID.
  • "REST is stateless so you can't authenticate" — including auth info in every request (Bearer token) is compatible. Only server-side sessions are forbidden.
  • "REST is older than GraphQL" — they solve different problems. REST is resource-oriented, GraphQL is query-oriented. See how-graphql-actually-works.

Real-World REST Trade-offs

  • Over-fetching — /users/42 returns every field → wasted bytes on mobile. Solved by sparse fieldsets (?fields=id,name) or GraphQL.
  • Under-fetching (N+1) — list + per-item detail = N round trips. Mitigated by embedding (?include=author) or batch endpoints.
  • Versioning — path versioning (/v1/users, /v2/users) is common. Header versioning (Accept-Version) is also possible but less discoverable.
  • Pagination consistency — offset vs cursor trade-offs are in how-pagination-actually-works.
  • CORS — browser clients calling cross-origin need preflight + header negotiation. See cors-explained.
  • Rate limits / abuse — public REST APIs need throttling. See rate-limiting-strategies.

Wrap-up

The industry consensus: "REST API = HTTP + JSON + noun URLs + standard methods". That's Richardson Level 2 — pragmatic and plenty good as a starting point. But it's not all of Fielding's REST. HATEOAS / true hypermedia is what gets you to Level 3.

In practice, "the team and clients agreeing on a consistent set of rules" matters more than "satisfying every REST constraint". Method obsession and HATEOAS evangelism don't help much. That said, three things are essentially free wins: use methods with their actual semantics, use status codes meaningfully, and treat URLs as resource identifiers.

Back to guides