Episode 3 — NodeJS MongoDB Backend Architecture / 3.9 — REST API Development
3.9.a — What is a REST API?
In one sentence: REST (REpresentational State Transfer) is an architectural style for networked applications that uses stateless, resource-oriented communication over HTTP — and most "REST APIs" you encounter are actually REST-like rather than fully RESTful.
Navigation: <- 3.9 Overview | 3.9.b — API Versioning ->
1. What REST Stands For
REST = REpresentational State Transfer
- Coined by Roy Fielding in his 2000 PhD dissertation
- It is an architectural style, not a protocol, library, or framework
- REST describes constraints that, when followed, produce systems with desirable properties: scalability, simplicity, modifiability, portability
| Common misconception | Reality |
|---|---|
| "REST is a protocol" | REST is a set of architectural constraints |
| "REST requires JSON" | REST is format-agnostic; JSON is just the most popular representation |
| "REST requires HTTP" | REST can theoretically work over any protocol, but HTTP is the standard transport |
| "Any API with URLs is REST" | An API must follow REST constraints to be RESTful |
2. The 6 REST Constraints
REST defines six constraints. An API that follows all of them is truly RESTful.
2.1 Client-Server
CLIENT (browser, mobile app) SERVER (API backend)
| |
| --- HTTP Request ---> |
| <--- HTTP Response --- |
| |
(UI concerns) (data/logic concerns)
- Separation of concerns: client handles UI, server handles data storage and business logic
- Either side can evolve independently
2.2 Stateless
Every request from client to server must contain all information needed to understand and process it. The server stores no session state between requests.
// STATELESS: token carries identity in every request
GET /api/users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// NOT STATELESS: server remembers "who you are" from a previous request
// without any identifying information in the current request
Benefits: scalability (any server can handle any request), reliability, visibility.
2.3 Cacheable
Responses must define themselves as cacheable or non-cacheable. If cacheable, the client (or intermediary) can reuse response data for equivalent future requests.
HTTP/1.1 200 OK
Cache-Control: max-age=3600, public
ETag: "v1-user-42"
2.4 Uniform Interface
The most distinctive REST constraint. It simplifies architecture through four sub-constraints:
| Sub-constraint | Meaning |
|---|---|
| Resource identification | Each resource has a unique URI (/api/users/42) |
| Resource manipulation through representations | Client sends/receives representations (JSON, XML) to modify resources |
| Self-descriptive messages | Each message contains enough info to process it (Content-Type, methods) |
| HATEOAS | Hypermedia as the engine of application state (links in responses guide the client) |
2.5 Layered System
The client cannot tell whether it is connected directly to the origin server or an intermediary (load balancer, CDN, API gateway). Each layer only knows about the layer it interacts with.
Client -> CDN -> Load Balancer -> API Gateway -> App Server -> Database
2.6 Code on Demand (Optional)
Servers can extend client functionality by sending executable code (e.g., JavaScript). This is the only optional constraint.
3. Resources and URIs
In REST, everything is a resource — a conceptual entity identified by a URI.
Resource naming conventions
| Pattern | Example | Description |
|---|---|---|
| Collection | /api/users | All users |
| Specific resource | /api/users/42 | User with ID 42 |
| Sub-collection | /api/users/42/posts | All posts by user 42 |
| Specific sub-resource | /api/users/42/posts/7 | Post 7 by user 42 |
URI best practices
GOOD BAD
---- ---
/api/users /api/getUsers (verb in URL)
/api/users/42 /api/user/42 (inconsistent plural)
/api/users/42/posts /api/getUserPosts (RPC-style)
/api/users?role=admin /api/adminUsers (filter in URL path)
Rule of thumb: nouns for resources, HTTP methods for actions.
| HTTP Method | CRUD Operation | Example |
|---|---|---|
GET | Read | GET /api/users |
POST | Create | POST /api/users |
PUT | Replace entirely | PUT /api/users/42 |
PATCH | Partial update | PATCH /api/users/42 |
DELETE | Delete | DELETE /api/users/42 |
4. Representations: JSON, XML, HTML
A representation is how a resource's state is transferred between client and server.
// JSON representation (most common for APIs)
{
"id": 42,
"name": "Alice",
"email": "alice@example.com",
"createdAt": "2026-01-15T10:30:00Z"
}
<!-- XML representation (older APIs, SOAP) -->
<user>
<id>42</id>
<name>Alice</name>
<email>alice@example.com</email>
</user>
Content negotiation via headers:
# Client requests JSON
Accept: application/json
# Server responds with JSON
Content-Type: application/json
5. RESTful vs REST-like
| Aspect | Truly RESTful | REST-like (most APIs) |
|---|---|---|
| Stateless | Yes | Yes |
| Resource URIs | Yes | Yes |
| HTTP methods | Correct usage | Usually correct |
| HATEOAS | Yes (links in responses) | No (clients hardcode URLs) |
| Hypermedia-driven | Yes | No |
| Self-descriptive | Fully | Partially |
Reality: Most APIs marketed as "REST" are REST-like. They use HTTP methods and resource URLs but skip HATEOAS and full self-description. This is perfectly fine for most applications.
6. REST vs SOAP vs GraphQL
| Feature | REST | SOAP | GraphQL |
|---|---|---|---|
| Type | Architectural style | Protocol (W3C standard) | Query language |
| Transport | HTTP (typically) | HTTP, SMTP, TCP | HTTP (typically) |
| Data format | JSON, XML, any | XML only | JSON |
| Schema | Optional (OpenAPI) | Required (WSDL) | Required (SDL) |
| Overfetching | Common problem | Same issue | Solved (client picks fields) |
| Underfetching | Multiple requests needed | Same issue | Single query |
| Caching | HTTP caching built-in | Complex | Requires extra tooling |
| Learning curve | Low | High | Medium |
| Best for | CRUD-heavy apps, public APIs | Enterprise, banking, WS-Security | Complex data graphs, mobile apps |
// REST: multiple endpoints, fixed response shapes
GET /api/users/42 // returns full user object
GET /api/users/42/posts // separate request for posts
// GraphQL: single endpoint, flexible queries
POST /graphql
{
query: `{
user(id: 42) {
name
email
posts { title }
}
}`
}
7. Richardson Maturity Model
Leonard Richardson proposed a model to grade API maturity toward REST:
Level 3: Hypermedia Controls (HATEOAS) <-- Truly RESTful
Level 2: HTTP Verbs (GET, POST, PUT, DELETE) <-- Most "REST" APIs
Level 1: Resources (distinct URIs)
Level 0: The Swamp of POX (one endpoint, one method)
Level 0 — Single endpoint
POST /api
{ "action": "getUser", "userId": 42 }
Level 1 — Resources
POST /api/users/42
{ "action": "get" }
Level 2 — HTTP Verbs
GET /api/users/42
DELETE /api/users/42
Level 3 — HATEOAS
{
"id": 42,
"name": "Alice",
"email": "alice@example.com",
"_links": {
"self": { "href": "/api/users/42" },
"posts": { "href": "/api/users/42/posts" },
"delete": { "href": "/api/users/42", "method": "DELETE" }
}
}
HATEOAS (Hypermedia As The Engine Of Application State) means the server response contains links that tell the client what actions are possible next. The client does not need to hardcode URL patterns.
8. Real-World REST API Examples
GitHub API
# List repositories for a user
curl https://api.github.com/users/octocat/repos
# Response includes hypermedia links
{
"id": 1296269,
"name": "Hello-World",
"url": "https://api.github.com/repos/octocat/Hello-World",
"commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}"
}
Stripe API
# Create a customer
curl https://api.stripe.com/v1/customers \
-u sk_test_xxx: \
-d email="alice@example.com"
# Stripe uses URL versioning in headers
# Stripe-Version: 2023-10-16
Twitter/X API v2
# Get a user by username
curl "https://api.twitter.com/2/users/by/username/jack" \
-H "Authorization: Bearer $TOKEN"
Common patterns across real APIs:
- JSON as primary format
- Bearer token authentication
- Pagination via query params (
?page=2&limit=20) - Rate limiting with headers (
X-RateLimit-Remaining) - Versioning in URL or headers
9. Building a Basic REST API in Express
const express = require('express');
const app = express();
app.use(express.json());
// In-memory data store (use MongoDB in production)
let users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
];
// GET /api/users — List all users
app.get('/api/users', (req, res) => {
res.json({ data: users, count: users.length });
});
// GET /api/users/:id — Get a single user
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: { message: 'User not found' } });
res.json({ data: user });
});
// POST /api/users — Create a new user
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: { message: 'Name and email are required' } });
}
const newUser = { id: users.length + 1, name, email };
users.push(newUser);
res.status(201).json({ data: newUser });
});
// PUT /api/users/:id — Replace a user entirely
app.put('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) return res.status(404).json({ error: { message: 'User not found' } });
users[index] = { id: parseInt(req.params.id), ...req.body };
res.json({ data: users[index] });
});
// PATCH /api/users/:id — Partial update
app.patch('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: { message: 'User not found' } });
Object.assign(user, req.body);
res.json({ data: user });
});
// DELETE /api/users/:id — Delete a user
app.delete('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) return res.status(404).json({ error: { message: 'User not found' } });
users.splice(index, 1);
res.status(204).send();
});
app.listen(3000, () => console.log('REST API running on port 3000'));
10. Key Takeaways
- REST is an architectural style with 6 constraints — not a protocol or library.
- Resources are identified by URIs; HTTP methods map to CRUD operations.
- Most real-world APIs are REST-like (Level 2) — they skip HATEOAS and that is fine.
- REST excels at CRUD-heavy apps; GraphQL excels at complex data fetching; SOAP is for enterprise with strict contracts.
- The Richardson Maturity Model measures how "RESTful" an API truly is (Levels 0-3).
Explain-It Challenge
Explain without notes:
- What are the 6 REST constraints and why does statelessness matter for scalability?
- Why are most APIs REST-like rather than truly RESTful, and is that a problem?
- When would you choose GraphQL over REST, and vice versa?
Navigation: <- 3.9 Overview | 3.9.b — API Versioning ->