REST API Design Explained: Constraints, HTTP Methods, Idempotency & HATEOAS (Visualized)
REST is an architectural style where clients exchange representations of named resources with a stateless server over HTTP. This guide covers Fielding's six constraints, resources, HTTP methods, idempotency and safety, HATEOAS and the Richardson Maturity Model, and REST vs RPC/GraphQL — with live animations.
REST (Representational State Transfer) is an architectural style for distributed systems in which clients exchange representations of named resources with a stateless server over a uniform interface — almost always HTTP.
REST was defined by Roy Fielding in his 2000 doctoral dissertation, which also formalized the web's own architecture. It is not a protocol or a standard; it is a set of constraints that, when applied to an HTTP API, tend to produce systems that are scalable, evolvable, and cacheable. When people say "REST API" they usually mean an HTTP API that exposes resources at URLs and uses HTTP methods and status codes — though, as we will see, most real APIs only follow the constraints partway.
Roy Fielding's Six Constraints
REST is defined by applying constraints to a network architecture. Five are required; one (code-on-demand) is optional. An API that violates a required constraint is, strictly speaking, not RESTful — regardless of how many JSON endpoints it exposes.
| Constraint | What it requires |
|---|---|
| Client–server | Separate the UI/consumer from data storage so each evolves independently. |
| Stateless | Each request contains everything needed to process it; the server stores no client session context between requests. |
| Cacheable | Responses must declare themselves cacheable or not, so clients and intermediaries can reuse them. |
| Uniform interface | One consistent way to identify and manipulate resources (URLs, methods, representations, hypermedia). |
| Layered system | A client cannot tell whether it is connected to the origin server or an intermediary (proxy, cache, gateway). |
| Code-on-demand (optional) | Servers may extend clients by shipping executable code, e.g. JavaScript. |
The uniform interface is the constraint that most distinguishes REST from other styles. It has four sub-constraints: identification of resources, manipulation through representations, self-descriptive messages, and hypermedia as the engine of application state (HATEOAS). Together they decouple clients from servers so the API can change without breaking consumers.
Resources and Representations
The core abstraction in REST is the resource: any concept worth naming — a user, an order, a collection of products. Each resource is identified by a URL, e.g. /users/42. The client never manipulates the resource directly; it exchanges a representation of it — typically a JSON or XML document describing the resource's current state. The same resource can have several representations (JSON, XML, HTML), negotiated via the Accept header.
This separation is why REST URLs name things, not actions. /users/42 is a resource; /getUser?id=42 is a remote procedure call wearing a URL. In REST the verb lives in the HTTP method, not the path.
The Request–Response Cycle
Every REST interaction is a request from client to server and a single response back. The request names a method and a path (and optionally a body and headers); the response carries a status code and a representation. The animation below shows a sequence of calls against a /users resource — each one self-contained, each answered with a status code.
HTTP Methods, Safety, and Idempotency
REST maps operations onto standard HTTP methods. Two properties matter for correctness. A method is safe if it has no observable side effects (read-only). A method is idempotent if making the same request many times has the same effect as making it once — crucial for retries over an unreliable network: a client that times out can safely re-send an idempotent request.
| Method | Safe? | Idempotent? | Typical use |
|---|---|---|---|
| GET | Yes | Yes | Retrieve a resource or collection |
| HEAD | Yes | Yes | Like GET but headers only |
| POST | No | No | Create a subordinate resource / non-idempotent action |
| PUT | No | Yes | Create or fully replace a resource at a known URL |
| PATCH | No | No | Partially update a resource |
| DELETE | No | Yes | Remove a resource |
Note that POST is neither safe nor idempotent — calling it twice usually creates two resources. PUT and DELETE are idempotent: replacing a resource with the same body twice, or deleting an already-deleted resource, leaves the same end state. Common status codes pair with these: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 404 Not Found, 409 Conflict, and 500 Internal Server Error.
# Safe + idempotent read
curl -s https://api.example.com/users/42
# Create (not idempotent: runs twice => two resources)
curl -X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-d '{"name":"Ada"}'
# Full replace at a known URL (idempotent)
curl -X PUT https://api.example.com/users/42 \
-H 'Content-Type: application/json' \
-d '{"name":"Ada Lovelace"}'
# Idempotent delete
curl -X DELETE https://api.example.com/users/42Statelessness
The statelessness constraint says the server must not store client session state between requests. Every request carries its own context — authentication token, parameters, and body — so any server in a pool can handle any request. This is what makes REST APIs trivial to scale horizontally: there is no sticky session to pin a client to one machine. The cost is that clients must re-send context on every call, which is why bearer tokens travel in each request's headers.
Cacheability
Because safe methods like GET have no side effects, their responses can be cached. A server marks a response cacheable with headers such as Cache-Control, ETag, and Expires. A cache — in the browser, a CDN, or a reverse proxy — can then serve a repeated request without ever contacting the origin. Caching is one of REST's biggest performance wins, and it is only possible because of the uniform interface and statelessness.
HATEOAS and the Richardson Maturity Model
HATEOAS (Hypermedia As The Engine Of Application State) is the final, most-ignored part of the uniform interface. It says a response should include links to the actions a client can take next, so the client navigates the API by following hyperlinks rather than hard-coding URLs. In a fully RESTful API, a client needs to know only one entry-point URL and the media type — the server drives the rest.
Leonard Richardson's Maturity Model grades how far an API climbs toward this ideal:
| Level | Name | What it adds |
|---|---|---|
| 0 | The Swamp of POX | One URL, one method (usually POST) — RPC over HTTP. |
| 1 | Resources | Many URLs, one per resource, but still one verb. |
| 2 | HTTP Verbs | Proper use of GET/POST/PUT/DELETE and status codes. Most "REST" APIs stop here. |
| 3 | Hypermedia (HATEOAS) | Responses contain links; the client follows them. True REST. |
Fielding has argued that an API is not truly RESTful unless it reaches Level 3. In practice the overwhelming majority of production "REST" APIs sit at Level 2 — and that is usually a pragmatic, defensible choice.
REST vs RPC and GraphQL
RPC styles (gRPC, JSON-RPC) expose functions — createUser(), getOrders() — typically over a single endpoint. They are efficient and a great fit for tightly coupled internal services, but they lack REST's uniform interface and HTTP-level cacheability. GraphQL exposes a single endpoint and lets the client specify exactly which fields it needs in one query, solving over- and under-fetching; the trade-off is that HTTP caching and the uniform interface no longer apply, and you take on a query layer of your own.
| Aspect | REST | RPC (e.g. gRPC) | GraphQL |
|---|---|---|---|
| Core abstraction | Resources (nouns) | Procedures (verbs) | A typed graph / query |
| Endpoints | Many, one per resource | One (or one per service) | Usually one |
| HTTP caching | Built-in via methods/headers | Manual | Hard (POST queries) |
| Over/under-fetching | Common | Tuned per call | Client picks fields |
| Best fit | Public, evolvable web APIs | Internal microservices | Rich, varied client data needs |
How Real APIs Deviate from Pure REST
Few production APIs are "pure" REST, and that is fine. Common, pragmatic deviations include: action endpoints like POST /orders/42/cancel when an operation does not map cleanly to a CRUD verb; filtering, sorting, and pagination via query strings (?status=open&page=2); compound or nested responses that embed related resources to avoid extra round trips; and skipping HATEOAS because clients are versioned and shipped together with the server. The goal of an API is to serve its clients well — the constraints are a guide, not a religion.
Frequently Asked Questions
Is REST the same as HTTP?
No. REST is an architectural style; HTTP is a protocol. REST can in principle run over other protocols, but in practice it is almost always implemented over HTTP because HTTP's methods, status codes, and caching headers map so naturally onto REST's constraints. You can also use HTTP in a non-RESTful way — for example, tunneling RPC calls through a single POST endpoint.
Does a JSON API automatically make it RESTful?
No. The data format is irrelevant to REST — you can build a RESTful API with XML or HTML, and a deeply non-RESTful one with JSON. What matters is whether you honor the constraints: resource-oriented URLs, correct use of HTTP methods and status codes, statelessness, cacheability, and (for full REST) hypermedia. Most "JSON REST" APIs reach Richardson Level 2, not Level 3.
Why does idempotency matter for REST APIs?
Because networks fail. If a client sends a request and the connection drops before the response arrives, it cannot tell whether the server applied the change. For idempotent methods (GET, PUT, DELETE) the client can simply retry with no risk of duplication. For non-idempotent ones like POST, you typically add an idempotency key so the server can recognize and de-duplicate retries.
REST is not about JSON over HTTP — it is a small set of constraints that buy you scalability, caching, and decades of evolvability. Most APIs borrow the good parts and stop at Level 2, and that is usually the right call.
— alokknight Engineering
