REST Principles, Applied Pragmatically
By the end of this lesson
Apply REST where it helps and depart from it deliberately where it does not.
REST is an architectural style: a set of constraints that, if you accept them, tend to produce APIs that are predictable and that scale out easily. It is not a protocol, a library or a framework. Nothing you install makes an API RESTful, and nothing stops you from ignoring parts of it.
Three of its ideas do most of the useful work. Things in your system get addresses. A small, fixed set of methods operates on those addresses. Each request stands on its own. If you take only those three from this lesson, your API will be easier to consume than most.
The vocabulary, defined plainly:
- Resource
- A thing worth addressing: an employee, a department, an order. Each has an identifier, and /api/employees/42 means that employee and nothing else.
- Representation
- The form in which you send a resource — usually a JSON document. The resource is the employee; the JSON is one view of it. They are not the same, which is why your JSON does not have to mirror your table.
- Uniform interface
- The same small set of methods applies to every resource. Once a caller has learned how GET, POST, PUT, PATCH and DELETE behave on employees, it already knows how they behave on departments.
- Statelessness
- The server keeps no memory of the caller between requests. Every request carries everything needed to understand it — credentials, ids, parameters.
Statelessness is the constraint with the most practical consequences, so it is worth being concrete. It rules out a server-side session that remembers "the employee this caller was editing" or "step three of the wizard". If a sequence of calls needs shared state, that state lives in the database, or in a cache both instances can read, or in the request itself.
What you gain is that any instance can serve any request. You can run five copies behind a load balancer, restart one during a deployment, or scale to twenty at month end, and no caller notices — there is no session pinned to the instance that just went away.
What you pay is repetition. Every request re-sends its token, and the server validates it again. Work that a session would have done once now happens on every call. That cost is usually small and predictable, and it buys you the ability to add capacity by adding instances.
# The uniform interface doing ordinary work
GET /api/employees/42
GET /api/departments/3/employees
POST /api/orders
PATCH /api/employees/42
DELETE /api/orders/1042
# Cancelling an order. Two defensible designs:
POST /api/orders/1042/cancellations # a cancellation is a record
POST /api/orders/1042/cancel # a plain instruction
# A contortion, invented only to avoid a verb:
POST /api/payroll-runs/2024-09/recalculation-requests- The first block needs no explanation from you. A caller who has used one resource can predict the rest, which is the entire point of the uniform interface.
- The first cancellation design treats the cancellation as a thing: it has a reason, a timestamp and a person who did it, and you may well want to list them. When the action produces a record worth reading back, the noun is genuine and a POST that creates it fits.
- The second design is a plain instruction. It reads clearly, it documents itself, and every consumer understands it immediately. It is not strictly REST, and it is a reasonable choice when there is no cancellation record to expose.
- The third is the failure mode to watch for. Nobody in the business says "recalculation request". The noun exists only to satisfy a rule, and it makes the endpoint harder to find, harder to document and harder to discuss.
Being RESTful is not itself a goal. The goal is an API that callers can predict and that you can change safely. REST is a well-tested route to that, which is why it is the sensible default — but when a constraint stops serving the goal, the constraint is what gives way.
The test to apply is practical rather than doctrinal: would an experienced developer who has never seen your API guess this endpoint correctly, and would they guess what it does? A resource-shaped design usually wins that test. Occasionally a clearly named action wins it instead, and then the action is the better design.
One further part of Fielding's model, hypermedia, deserves an honest mention. In the full model a response includes links to the actions available next, so a client can navigate without hardcoding paths. Most APIs omit it, because it adds real work to both sides and few clients take advantage of the links. Knowing it exists matters more than adopting it.
Summary
- REST is a style built from constraints, not something a library gives you
- Resources have addresses, a small fixed set of methods acts on them, and each request stands alone
- Statelessness rules out server-side sessions and is what lets you scale out and restart freely
- Consistency is the real prize, because it removes decisions from every future endpoint
- An operation that is genuinely an action is better named clearly than forced into an invented noun
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Think about it
Design the approval
A manager approves a leave request. Propose two designs: one that models the approval as a resource, and one that uses a named action.
Say which you would ship, and what information would change your answer.
Show solution
As a resource: POST /api/leave-requests/88/approvals, creating an approval that records who approved it, when, and any comment. GET on the same path lists them.
As an action: POST /api/leave-requests/88/approve, which changes the request's status and returns the updated request.
The information that decides it is whether the approval is data anyone needs to read back. If an audit trail matters — and for leave approvals it usually does — the resource design is not a contortion, it is an accurate model, and it gives you the audit endpoint for free.
If nothing is ever read back and the status change is the whole story, the named action is shorter, clearer, and fine. Both are defensible. What is not defensible is choosing without asking the question.
Think about it
What statelessness costs you
An order is built over four screens. The team proposes holding the part-built order in server memory between calls, keyed by the caller's session.
Describe what breaks in production, and describe an approach that keeps the same user experience.
Show solution
With more than one instance behind a load balancer, screen two may land on an instance that has never seen screen one, so the part-built order is missing. Pinning each caller to one instance papers over it until a deployment restarts that instance and the data is gone.
The approach that keeps the experience is to make the draft a real resource. POST /api/orders creates it with a status of Draft, each screen PATCHes it, and a final call submits it. The state lives in the database, where it survives restarts and is visible to every instance.
You also gain something the session approach never offered: the customer can close the browser and come back, because the draft is stored rather than remembered.
Knowledge check
Nothing is recorded and there is no score. The explanation appears either way.
Saved in this browser only.