π REST Principles
Every time you open a weather app, refresh a feed, or check out a shopping cart, a client is talking to a server over HTTP in a style called REST. In this lesson you'll learn the small set of rules that make an API feel predictable β so predictable that a developer can guess how to use it before reading a single line of documentation.
Week 7 · Day 3 (Wednesday: RESTful API Design) · Lecture 1
π― Learning Objectives
By the end of this lesson, you will be able to:
- Define REST as an architectural style and explain where it came from
- Describe the six REST constraints and what each one buys you
- Explain statelessness and why it is the key to horizontal scaling
- Model a domain as resources named with nouns, not verbs
- Map CRUD operations onto HTTP methods and recognize safe vs. idempotent
- Judge an API against the Richardson Maturity Model
Estimated Time: 60 minutes
Practice: Redesign a verb-based API into a clean resource-oriented one, then sketch a bookstore resource model.
In This Lesson
What Is REST?
REST stands for Representational State Transfer. It is not a protocol, a library, or a standard you install β it is an architectural style: a set of constraints that, when you follow them, produce web APIs that are simple, scalable, and durable. Roy Fielding described it in his year-2000 doctoral dissertation, essentially writing down the design principles that already made the web itself work so well.
An API that follows these constraints is called RESTful. The core idea is disarmingly simple: model everything in your system as a resource (a user, an order, a photo), give each resource a URL, and manipulate it with the standard HTTP methods the web already speaks.
π The Library Analogy
Think of a REST API as a well-run library. The books are resources, shelved by topic into collections. Each book has a unique call number (its URL). The things you can do are a fixed vocabulary: browse a shelf without touching it (GET), donate a new book (POST), swap in a new edition (PUT), correct a typo on the cover (PATCH), or pull a book from circulation (DELETE). The librarian answers with standard phrases β "here you go" (200), "we don't have that" (404), "that section is restricted" (403). You already know how to use any library because they all share this vocabulary. A RESTful API aims for exactly that familiarity.
Because REST rides on plain HTTP, it works with every browser, every programming language, every proxy and cache on the internet β no special tooling required. That universality is a big part of why REST beat heavier alternatives like SOAP for public web APIs.
The Six REST Constraints
Fielding defined REST through six constraints. Five are required; one (code-on-demand) is optional. Each constraint trades a little freedom for a lot of predictability.
| Constraint | What it means | Why it matters |
|---|---|---|
| ClientβServer | Separate the UI (client) from data storage (server). | Each side evolves independently β redesign the app without touching the API. |
| Stateless | Each request carries everything needed to fulfill it. | Any server instance can handle any request, so you scale by adding servers. |
| Cacheable | Responses declare whether they may be reused. | Fewer round trips, faster clients, lighter servers. |
| Uniform Interface | One consistent way to identify and manipulate resources. | The API is learnable and self-descriptive. |
| Layered System | A client can't tell if it talks to the origin or an intermediary. | Load balancers, gateways, and caches slot in invisibly. |
| Code on Demand (optional) | The server may ship executable code (e.g. JavaScript) to the client. | Extends client behavior on the fly; rarely used in JSON APIs. |
The Uniform Interface β the heart of REST
Of the six, the uniform interface is what makes REST feel like REST. It has four sub-rules:
- Identification of resources β each resource has a stable URL, e.g.
/users/123. - Manipulation through representations β the client works with a JSON (or XML) representation of the resource, not the resource itself.
- Self-descriptive messages β each message carries enough metadata (method, headers, media type) to be understood on its own.
- HATEOAS β Hypermedia As The Engine Of Application State: responses include links telling the client what it can do next.
π‘ Representation vs. resource
The resource is the abstract thing β "user 123." Its representation is a concrete snapshot the server sends you, usually JSON. The same resource might be represented as JSON for an app, XML for a legacy client, or HTML for a browser. The URL points at the resource; content negotiation picks the representation.
Statelessness in Depth
Statelessness is the constraint people misunderstand most, so let's slow down. Stateless means the server keeps no memory of previous requests between calls. Every request must carry all the context needed to process it β most importantly, who you are.
Compare two ways to remember a logged-in user:
# β Stateful: the server stores a session in memory, keyed by a cookie.
# Request 2 only works if it lands on the SAME server that handled login.
GET /api/orders HTTP/1.1
Cookie: sessionId=abc123 # server must look this up in its own memory
# β
Stateless: the request carries a self-contained token every time.
# ANY server instance can verify it β no shared memory required.
GET /api/orders HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsIn...
Why does this unlock scaling? If the server remembers nothing, then request #1 and request #2 can be handled by different machines. You put a load balancer in front of ten identical servers, and it can send each request wherever there's capacity. Need more throughput? Add an eleventh server. This is horizontal scaling, and statelessness is what makes it painless.
β οΈ "Stateless" is about the connection, not the data
Statelessness does not mean your app has no data or that nothing is ever saved. Application state (your orders, your profile) lives in the database. What the constraint forbids is conversational state β the server remembering "this connection is halfway through step 3." Each request stands alone.
Resources: Nouns, Not Verbs
The single most important design habit in REST: URLs name things, HTTP methods do things. A URL should be a noun β a resource β and the verb comes from the method. The moment you see a verb in a path, something has gone wrong.
| β Verb in the URL (RPC-style) | β Noun + HTTP method (RESTful) |
|---|---|
GET /getUser?id=123 | GET /users/123 |
POST /createUser | POST /users |
POST /updateUser | PUT /users/123 |
POST /deleteUser?id=123 | DELETE /users/123 |
GET /listActiveUsers | GET /users?status=active |
Collections and members
Two URL shapes cover almost everything:
- A collection β plural noun:
/users,/products,/orders. - A member of that collection β collection plus an identifier:
/users/123.
Resources relate to one another, and hierarchy shows that relationship:
GET /posts # all blog posts
GET /posts/42 # one specific post
GET /posts/42/comments # all comments ON post 42
GET /posts/42/comments/7 # comment 7 on post 42
GET /users/9/posts # all posts BY user 9
β Resource naming rules of thumb
- Plural nouns for collections:
/users, not/user. - lowercase, kebab-case for multi-word names:
/product-categories. - Query strings for filtering/sorting/paging:
/users?role=admin&sort=name. - Cap nesting at 2β3 levels. Instead of
/users/9/posts/42/comments/7/likes, expose/comments/7/likesor/likes?commentId=7.
HTTP Methods & CRUD
REST maps the four classic data operations β Create, Read, Update, Delete β onto HTTP methods. Because the method carries the intent, the same URL can serve several operations.
| Method | CRUD | On /users | On /users/123 |
|---|---|---|---|
| GET | Read | List all users | Fetch user 123 |
| POST | Create | Create a new user | β (rarely used) |
| PUT | Replace | β (rare) | Replace user 123 entirely |
| PATCH | Partial update | β (rare) | Update some fields of 123 |
| DELETE | Delete | β (dangerous) | Remove user 123 |
Two properties worth memorizing: safe and idempotent
- Safe β the request does not change server state. It's read-only. Only
GETandHEADare safe. - Idempotent β making the same request many times has the same effect as making it once.
GET,PUT, andDELETEare idempotent;POSTis not.
| Method | Safe? | Idempotent? |
|---|---|---|
| GET | β Yes | β Yes |
| POST | β No | β No |
| PUT | β No | β Yes |
| PATCH | β No | β οΈ Not necessarily |
| DELETE | β No | β Yes |
π‘ Why idempotency is practical, not academic
Networks drop responses. If a client sends DELETE /users/123 and never hears back, it can safely retry β because deleting an already-deleted user still leaves it deleted. But retrying POST /users might create a second user. That's the whole difference: idempotent methods are safe to retry, and POST is the one you must protect against duplicate submissions.
Here is the mapping expressed in Express, the Node.js framework you'll use all week:
const express = require('express');
const app = express();
app.use(express.json()); // parse JSON request bodies
// READ the whole collection
app.get('/users', (req, res) => {
res.status(200).json(getAllUsers());
});
// READ one member
app.get('/users/:id', (req, res) => {
const user = getUserById(req.params.id);
if (!user) return res.status(404).json({ error: 'Not Found' });
res.status(200).json(user);
});
// CREATE β POST to the collection, respond 201 + Location header
app.post('/users', (req, res) => {
const user = createUser(req.body);
res.status(201).location(`/users/${user.id}`).json(user);
});
// REPLACE the whole member
app.put('/users/:id', (req, res) => {
const user = replaceUser(req.params.id, req.body);
res.status(200).json(user);
});
// PARTIAL update
app.patch('/users/:id', (req, res) => {
const user = updateUser(req.params.id, req.body);
res.status(200).json(user);
});
// DELETE β 204 means "success, nothing to return"
app.delete('/users/:id', (req, res) => {
deleteUser(req.params.id);
res.status(204).end();
});
Notice the shape: the same URLs, different methods. That uniformity is the payoff of following the constraints. (The exact status codes get a lesson of their own β that's next.)
The Richardson Maturity Model
Not every "REST API" is equally RESTful. Leonard Richardson proposed a four-level scale (popularized by Martin Fowler) that measures how fully an API embraces the constraints. It's a handy way to grade your own designs.
- Level 0 β The Swamp of POX. A single endpoint, everything is
POST, the body says what to do. This is remote-procedure-call dressed up as HTTP. - Level 1 β Resources. You introduce many URLs (
/users,/orders) but still lean on one method. - Level 2 β HTTP Verbs. You use
GET/POST/PUT/DELETEcorrectly and return meaningful status codes. Most production APIs live here β and that's perfectly respectable. - Level 3 β Hypermedia (HATEOAS). Responses include links to related resources and available actions, so clients discover the API by following links rather than hard-coding URLs.
// A Level 3 response embeds links telling the client what it can do next
{
"id": 123,
"name": "Ada Lovelace",
"status": "active",
"_links": {
"self": { "href": "/users/123" },
"orders": { "href": "/users/123/orders" },
"deactivate": { "href": "/users/123/deactivate", "method": "POST" }
}
}
π Aim for Level 2, reach for Level 3 when it pays
Hitting Level 2 β correct resources, correct methods, correct status codes β gives you nearly all the practical benefits of REST. Level 3 hypermedia is elegant and powerful for evolvable public APIs, but adds complexity many internal APIs don't need. Know it exists; apply it deliberately.
Practice & Quiz
ποΈ Exercise 1: De-verb an API
Goal: A junior teammate shipped this RPC-style API. Rewrite each line into a RESTful equivalent using resource nouns and the right HTTP method.
POST /getAllProducts
POST /getProductById?id=88
POST /addProduct
POST /editProduct?id=88
POST /removeProduct?id=88
POST /getReviewsForProduct?id=88
π‘ Hint
Turn each action into a plural noun + method. Reviews belong to a product, so nest them under that product. Filtering by id becomes a path segment, not a query string, when you're identifying one member.
β Solution
GET /products # was getAllProducts
GET /products/88 # was getProductById?id=88
POST /products # was addProduct
PUT /products/88 # was editProduct (or PATCH for a partial edit)
DELETE /products/88 # was removeProduct
GET /products/88/reviews # was getReviewsForProduct?id=88
Every verb moved out of the URL and into the HTTP method β the essence of a Level 2 REST API.
ποΈ Exercise 2: Model a bookstore
Goal: Design the resource URLs for a bookstore with books, authors, and reviews. An author writes many books; a book has many reviews. List the endpoints for reading the collections and members, plus the two nesting relationships.
β Solution
GET /authors # all authors
GET /authors/12 # one author
GET /authors/12/books # books BY author 12
GET /books # all books
GET /books/500 # one book
GET /books/500/reviews # reviews OF book 500
GET /reviews/9 # a review, directly (flattened access)
Notice nesting stops at two levels. Reviews are also reachable directly at /reviews/9 so you never need a four-segment URL to edit one.
π― Quick Quiz
Question 1: Which URL best follows REST conventions for fetching a single order?
Question 2: What does it mean that DELETE is idempotent?
Question 3: Statelessness enables horizontal scaling becauseβ¦
Best Practices & Pitfalls
β Do
- Name resources with plural nouns and let HTTP methods supply the verbs
- Keep the server stateless β send auth on every request (token, not server session)
- Use consistent conventions across the whole API (casing, pluralization, error shape)
- Limit URL nesting to 2β3 levels; flatten deep relationships
- Match methods to CRUD and respect safe/idempotent semantics
β Don't
- Put verbs in paths (
/createUser,/deleteOrder) - Use
GETfor anything that changes data β it must stay safe - Store per-user conversation state in server memory between requests
- Mix singular and plural (
/userhere,/ordersthere) - Assume "uses JSON over HTTP" automatically means "RESTful" β check it against the constraints
β οΈ The classic mistake: a "REST" API that's really RPC
# These are HTTP + JSON, but NOT RESTful β they're RPC in disguise:
POST /api/doLogin
POST /api/fetchUserData
POST /api/updateEverything
One endpoint per function, everything a POST, verbs everywhere. This is Level 0 on the maturity model. It works, but you lose caching, uniform tooling, and predictability.
Summary
π Key Takeaways
- REST is an architectural style, not a protocol β six constraints that make APIs simple and scalable
- The uniform interface (stable URLs, representations, self-descriptive messages, HATEOAS) is REST's core
- Statelessness β each request self-contained β is what enables painless horizontal scaling
- URLs are nouns (resources); HTTP methods are the verbs
- Methods map to CRUD, and knowing which are safe and idempotent guides retries and caching
- The Richardson Maturity Model grades how RESTful an API really is β aim for Level 2
π Additional Resources
- restfulapi.net β REST API Tutorial
- MDN β An overview of HTTP
- Martin Fowler β The Richardson Maturity Model
- Roy Fielding β Architectural Styles (the original REST chapter)
π What's Next?
You know the philosophy; next you get precise about the vocabulary. The following lesson, HTTP Methods & Status Codes, digs into each method's exact semantics and the full set of response codes β 200, 201, 204, 400, 401, 403, 404, 409, 422, 500 β so your API always says exactly what happened.
π Great work!
You can now look at any API and tell how RESTful it truly is β and design one that other developers will find a pleasure to use.