REST Fundamentals
REST is an architectural style, not a protocol. Learn the constraints that make an HTTP API RESTful, see them at work with load balancers, caches and hypermedia links, and grade APIs with the Richardson Maturity Model.
🎯 What you'll learn
- The six REST constraints and what each one buys you
- Resources vs representations, and content negotiation
- HATEOAS and the Richardson Maturity Model
- How caching really works: Cache-Control, ETag and 304
📚 Before you start
1What REST Actually Is
REST (Representational State Transfer) is not a protocol, a library, or a file format — it is an architectural style defined by Roy Fielding in his 2000 doctoral dissertation. It describes a set of constraints that, when applied together to a client-server system built on HTTP, produce specific properties: scalability, a uniform interface, independent evolution of client and server, and cacheability.
"REST API" is shorthand for "an HTTP API that follows (most of) these constraints." Plenty of APIs are called "REST" while only loosely following the style — this topic covers what the style actually requires versus what has become colloquial usage.
Partners integrate with PetPal through endpoints like:
GET /v1/pets/77 → read one pet
GET /v1/owners/12/pets → list an owner's pets
POST /v1/orders → create an order
DELETE /v1/orders/482 → cancel/delete an orderThese are nouns (resources) acted on by the standard HTTP verbs. The rest of this lesson explains which constraints make this design REST and what each one buys you.
2The Six Constraints
| # | Constraint | What it means |
|---|---|---|
| 1 | Client-server | UI concerns and data storage concerns are separated; either can evolve independently. |
| 2 | Stateless | Each request from client to server must contain all the information needed to understand and process it. The server holds no client session state between requests. |
| 3 | Cacheable | Responses must implicitly or explicitly label themselves as cacheable or not, so clients (and intermediaries) can reuse response data. |
| 4 | Uniform interface | The core defining trait of REST — see section 3. |
| 5 | Layered system | A client can't tell whether it's connected directly to the origin server or to an intermediary (load balancer, cache, gateway) — layers can be added transparently. |
| 6 | Code-on-demand (optional) | Servers can temporarily extend client functionality by transferring executable code (e.g. client-side scripts). This is the only optional constraint. |
3The Uniform Interface — Four Sub-Constraints
The uniform interface is what makes REST "REST" rather than just "an HTTP API." It has four parts:
- Identification of resources — every piece of information the API exposes (a user, an order, a collection of orders) is a resource, addressed by a stable URI (
/orders/482). - Manipulation of resources through representations — a client never touches the resource directly; it exchanges representations of it (a JSON document, typically) and the server translates between representation and internal state.
- Self-descriptive messages — each message includes enough information to describe how to process it: a
Content-Typeheader telling the client/server how to parse the body, standard HTTP methods and status codes carrying well-known semantics. - HATEOAS (Hypermedia As The Engine Of Application State) — responses include links describing what the client can do next, so clients aren't hard-coded with URI structure. This is the most-skipped constraint in real-world "REST" APIs.
Because every resource is read with GET, created with POST, and so on, generic tools work with any REST API without custom code: browsers, CDNs, proxies, API gateways, monitoring and client libraries.
4Resource vs. Representation
A resource is the concept (the order with id 482). A representation is the serialized snapshot of that concept sent over the wire at a point in time.
GET /orders/482 HTTP/1.1
Host: api.example.com
Accept: application/jsonHTTP/1.1 200 OK
Content-Type: application/json
ETag: "a1b2c3"
{
"id": 482,
"status": "shipped",
"total": 129.99,
"items": [
{ "sku": "SKU-100", "qty": 2 }
]
}Same resource, different representation is entirely possible — the same order could be returned as XML, HAL+JSON, or a PDF invoice depending on Accept negotiation, without the underlying resource changing.
GET /orders/482
Accept: application/json → { "id": 482, "status": "shipped", ... }
GET /orders/482
Accept: application/pdf → %PDF-1.7 … (an invoice document)The resource (order 482) is the same; the server picks the representation through content negotiation.
5HATEOAS in Practice
A HATEOAS-compliant response embeds the actions available on the current resource as links, rather than the client hard-coding URL patterns:
{
"id": 482,
"status": "shipped",
"total": 129.99,
"_links": {
"self": { "href": "/orders/482" },
"cancel": { "href": "/orders/482/cancel", "method": "POST" },
"invoice": { "href": "/orders/482/invoice" }
}
}If status were "cancelled" instead, the cancel link would simply be absent — the client discovers valid transitions from the response itself instead of encoding business rules like "you can only cancel a shipped order" on the client side.
6The Richardson Maturity Model
Leonard Richardson's maturity model is a practical way to grade how "REST-ful" an API actually is, in four levels (0-3):
| Level | Name | Characteristics |
|---|---|---|
| 0 | The Swamp of POX | Single endpoint, everything is a POST, method name lives in the payload ({"action": "getOrder"}). This is RPC-over-HTTP, not REST. |
| 1 | Resources | Multiple endpoints, one per resource type (/orders, /users), but still mostly using POST for everything. |
| 2 | HTTP Verbs | Resources and correct use of HTTP methods and status codes (GET to read, POST to create, DELETE to remove, proper 2xx/4xx/5xx). Most real-world "REST APIs" stop here. |
| 3 | Hypermedia Controls (HATEOAS) | Level 2 plus responses that embed the links/actions a client can take next. This is what Fielding considered actually REST. |
7REST vs. RPC vs. SOAP (Quick Contrast)
| REST | RPC (e.g. gRPC) | SOAP | |
|---|---|---|---|
| Core unit | Resources (nouns) | Procedures/functions (verbs) | Operations (verbs) |
| Payload format | Usually JSON | Usually Protocol Buffers (binary) | XML (strict envelope) |
| Transport coupling | HTTP semantics (methods, status codes) are part of the contract | Transport-agnostic, HTTP/2 in gRPC's case, used only as a carrier | HTTP is just a carrier; the envelope carries everything |
| Contract | Often OpenAPI (descriptive, optional) | .proto file (mandatory, code-generated) | WSDL (mandatory, strict) |
| Typical use | Public/partner-facing web APIs | Internal service-to-service calls | Legacy enterprise/financial systems |
Under the HoodHow it works internally▾
Statelessness is enforced by what the protocol doesn't give you
HTTP itself has no built-in concept of a "session." A stateless REST server literally cannot rely on remembering anything about a specific TCP connection or a prior request from that client — each request is parsed, authenticated (usually via a bearer token or API key sent in that very request's headers), processed, and the connection can be dropped or reused for a totally different client on the next request thanks to connection pooling/keep-alive. This is precisely why "session cookies that the server looks up in server memory" are not RESTful — they reintroduce server-held client state. A stateless design instead puts everything needed to process the request — auth token, pagination cursor, filters — into the request itself (headers, query string, or body).
Cacheability is implemented via real HTTP headers, not a REST-specific mechanism
The "cacheable" constraint isn't abstract — it's implemented by two concrete HTTP mechanisms:
Cache-Control— declares freshness rules directly (Cache-Control: max-age=60, publictells any cache, including browsers and CDNs, this response can be reused for 60 seconds without re-asking the server).ETag+ conditional requests — the server computes an opaque fingerprint (ETag: "a1b2c3", typically a hash of the representation) for a resource. A client re-requesting the same resource sendsIf-None-Match: "a1b2c3". If the resource hasn't changed, the server can skip re-sending the body entirely and reply304 Not Modified, saving bandwidth while still confirming the client's cached copy is valid.
GET /orders/482 HTTP/1.1
If-None-Match: "a1b2c3"
HTTP/1.1 304 Not ModifiedHATEOAS is a media-type problem, not a JSON-structure problem
Plain application/json has no standardized concept of a "link." That's why real HATEOAS implementations use hypermedia-aware media types — application/hal+json (defines _links formally), application/vnd.api+json (JSON:API spec), or application/problem+json for errors (its own topic). The _links object shown in section 5 is really the HAL convention; without an agreed media type, "links in the JSON body" is just an informal convention the client has to know about in advance, which weakens the whole point of hypermedia (letting the client discover behavior instead of hard-coding it).
⚠️ Common Pitfalls
- Calling any JSON-over-HTTP API "REST" — an API with a single
/apiendpoint that dispatches on anactionfield in the body (Richardson level 0) is RPC, not REST, regardless of what it's marketed as. - Confusing "stateless" with "no persisted data" — statelessness is about the server not holding session/conversation state between requests, not about whether the server has a database. The order itself (persisted state) is completely fine; a server-side "current step in a multi-request wizard" is not.
- Skipping HATEOAS and calling it a REST API anyway — this is extremely common (most "REST APIs" are actually Richardson Level 2), and it's a reasonable pragmatic choice, but it's worth knowing you've made that trade-off rather than believing you've implemented "true REST."
- Treating the URI as an implementation detail the client should parse — e.g. clients constructing
/orders/${id}/cancelthemselves by string concatenation instead of following acancellink from the resource — reintroduces tight coupling REST was designed to avoid. - Assuming REST mandates JSON — REST is representation-agnostic; JSON just won because it's convenient, not because the style requires it.
RPC dressed up as REST
POST /api
{ "action": "cancelOrder", "id": 482 }
200 OK
{ "ok": false, "error": "not found" }Resource + verb + status code
POST /orders/482/cancel (a link from the order)
404 Not Found
Content-Type: application/problem+json
{ "title": "Order not found" }📋 Quick Reference
6 constraints: client-server, stateless, cacheable, uniform interface, layered system, code-on-demand (optional)
Uniform interface: resource identification, representations, self-descriptive messages, HATEOAS
Richardson Maturity Model: 0 (RPC-over-HTTP) -> 1 (resources) -> 2 (HTTP verbs) -> 3 (hypermedia/HATEOAS)
Cacheability mechanics: Cache-Control (freshness) + ETag/If-None-Match (validation, 304 Not Modified)
Most "REST APIs" in the wild = Richardson Level 2 (resources + verbs, no HATEOAS)🧠 Key takeaways
- REST is a style: client-server, stateless, cacheable, uniform interface, layered system, and optional code-on-demand.
- Statelessness lets any server handle any request, which is what makes horizontal scaling and restarts painless.
- Cacheability is real HTTP: Cache-Control for freshness, ETag/If-None-Match for validation (304).
- Most APIs are Richardson Level 2; HATEOAS (Level 3) moves the rules about valid actions onto the server, discovered through links.
🎤 Interview Q&A
Q1What makes an API RESTful?core▾
REST constraints: client-server, statelessness, cacheability, a uniform interface (resources identified by URIs, manipulated through representations, standard methods, self-descriptive messages, hypermedia), layered system, and optional code-on-demand. In practice, 'REST' usually means resource-oriented HTTP APIs using methods and status codes correctly.
- What's HATEOAS? Responses include links to related actions/resources, letting clients navigate without hard-coded URLs.
Q2What is the Richardson Maturity Model?core▾
Level 0: one endpoint, RPC over HTTP. Level 1: resources. Level 2: HTTP methods and status codes used properly. Level 3: hypermedia controls. Most production 'REST' APIs are level 2.
- Is level 3 necessary? Rarely in practice; level 2 with good docs is the norm.
Q3REST vs RPC style APIs?core▾
REST models nouns (resources) manipulated by standard verbs; RPC models actions (/sendInvoice). REST gets caching, uniformity and tooling; RPC (gRPC) fits internal, action-heavy or high-performance calls. Many REST APIs use RPC-style sub-resources for actions (POST /orders/42/cancel).
- Is
POST /orders/42/cancelacceptable? Yes, it's a pragmatic way to model state transitions.
Q4Why does statelessness matter?senior▾
Each request carries all context (auth token, parameters), so any server can handle it: easy horizontal scaling, load balancing and failover. Session state, if needed, lives in shared stores.
- Are cookies stateless? The cookie is sent with each request; the server-side session it references is state.
Q5What makes a REST API easy to use?core▾
Consistent naming and structure, predictable status codes and error format, good docs (OpenAPI) with examples, pagination and filtering conventions, versioning strategy, idempotency for retries, and sensible defaults.
- Biggest usability killer? Inconsistency across endpoints.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.
✅ Check yourself
Answer the questions — feedback is instant.
If-None-Match: "a1b2c3" and the resource is unchanged. The server should reply…cancel link is…