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 misconceptionReality
"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-constraintMeaning
Resource identificationEach resource has a unique URI (/api/users/42)
Resource manipulation through representationsClient sends/receives representations (JSON, XML) to modify resources
Self-descriptive messagesEach message contains enough info to process it (Content-Type, methods)
HATEOASHypermedia 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

PatternExampleDescription
Collection/api/usersAll users
Specific resource/api/users/42User with ID 42
Sub-collection/api/users/42/postsAll posts by user 42
Specific sub-resource/api/users/42/posts/7Post 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 MethodCRUD OperationExample
GETReadGET /api/users
POSTCreatePOST /api/users
PUTReplace entirelyPUT /api/users/42
PATCHPartial updatePATCH /api/users/42
DELETEDeleteDELETE /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

AspectTruly RESTfulREST-like (most APIs)
StatelessYesYes
Resource URIsYesYes
HTTP methodsCorrect usageUsually correct
HATEOASYes (links in responses)No (clients hardcode URLs)
Hypermedia-drivenYesNo
Self-descriptiveFullyPartially

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

FeatureRESTSOAPGraphQL
TypeArchitectural styleProtocol (W3C standard)Query language
TransportHTTP (typically)HTTP, SMTP, TCPHTTP (typically)
Data formatJSON, XML, anyXML onlyJSON
SchemaOptional (OpenAPI)Required (WSDL)Required (SDL)
OverfetchingCommon problemSame issueSolved (client picks fields)
UnderfetchingMultiple requests neededSame issueSingle query
CachingHTTP caching built-inComplexRequires extra tooling
Learning curveLowHighMedium
Best forCRUD-heavy apps, public APIsEnterprise, banking, WS-SecurityComplex 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

  1. REST is an architectural style with 6 constraints — not a protocol or library.
  2. Resources are identified by URIs; HTTP methods map to CRUD operations.
  3. Most real-world APIs are REST-like (Level 2) — they skip HATEOAS and that is fine.
  4. REST excels at CRUD-heavy apps; GraphQL excels at complex data fetching; SOAP is for enterprise with strict contracts.
  5. The Richardson Maturity Model measures how "RESTful" an API truly is (Levels 0-3).

Explain-It Challenge

Explain without notes:

  1. What are the 6 REST constraints and why does statelessness matter for scalability?
  2. Why are most APIs REST-like rather than truly RESTful, and is that a problem?
  3. When would you choose GraphQL over REST, and vice versa?

Navigation: <- 3.9 Overview | 3.9.b — API Versioning ->