Home
Backend from First Principles / Module 11 — RESTful API Design

RESTful API Design

Resources are nouns. Pagination strategies. Response shape standards. HATEOAS.


REST Principles

REST (Representational State Transfer) is an architectural style, not a protocol. Six constraints define it:

  1. Client-Server — Separation of concerns. UI from data storage.
  2. Stateless — Each request contains all info needed. No session state on server.
  3. Cacheable — Responses declare if they're cacheable.
  4. Uniform Interface — Resources identified by URIs, manipulated via representations.
  5. Layered System — Client can't tell if it's talking to the real server or a proxy.
  6. Code on Demand (optional) — Server can send executable code (JS) to clients.

Most "REST APIs" only follow 1-4. That's fine. True REST (HATEOAS) is rarely implemented.


Resource Design

Resources are nouns, not verbs. URLs identify things, HTTP methods describe the action.

Snippet
Bad:
  GET  /getUsers
  POST /createUser
  POST /deleteUser/123
Snippet
Good:
  GET    /users           → list users
  POST   /users           → create user
  GET    /users/123       → get user 123
  PUT    /users/123       → replace user 123
  PATCH  /users/123       → partially update user 123
  DELETE /users/123       → delete user 123
Snippet
Nested resources for relationships:
  GET /users/123/posts        → posts by user 123
  POST /users/123/posts       → create post for user 123
  GET /users/123/posts/456    → specific post by user 123

Don't nest deeper than 2 levels. /a/1/b/2/c/3/d/4 is unnavigable.


Pagination

Never return unbounded lists. Always paginate.

Snippet
Offset pagination (simple, standard):
  GET /users?page=2&limit=20
  Response: { data: [...], total: 500, page: 2, totalPages: 25 }
  Problem: Skipping offset=10000 still scans 10000 rows. Slow on large tables.
Snippet
Cursor pagination (for feeds, high performance):
  GET /users?cursor=eyJ1c2VySWQiOiI1MDB9&limit=20
  Response: { data: [...], nextCursor: "eyJ1c2VySWQiOiI1MjB9" }
  Benefits: O(1) regardless of position, stable results during inserts.
Snippet
Keyset pagination (similar to cursor, database-native):
  GET /users?after_id=500&limit=20
  SQL: WHERE id > 500 ORDER BY id LIMIT 20

Use offset for admin UIs. Use cursor/keyset for public feeds and infinite scroll.


Response Shape Standards

Consistency matters more than perfection. Pick a shape and never deviate.

Snippet
Success (single resource):
  { "data": { "id": "123", "name": "Alice", "email": "alice@example.com" } }
Snippet
Success (list):
  {
    "data": [...],
    "meta": { "total": 100, "page": 1, "limit": 20 }
  }
Snippet
Error:
  {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "Request validation failed",
      "details": [{ "field": "email", "message": "Invalid email" }]
    }
  }

Timestamp fields: always ISO 8601 UTC. "2024-01-15T10:30:00Z"
IDs: string (not integer) — future-proof and avoids JS precision issues.


HATEOAS

HATEOAS (Hypermedia as the Engine of Application State) is the most misunderstood REST constraint. A truly RESTful API returns links to related actions in every response:

JavaScript
{
  "data": {
    "id": "123",
    "status": "pending",
    "_links": {
      "self":    { "href": "/orders/123" },
      "cancel":  { "href": "/orders/123/cancel", "method": "POST" },
      "payment": { "href": "/orders/123/payment" }
    }
  }
}

The client discovers what it can do next from the response itself — no out-of-band documentation needed. GitHub's API partially implements this.

Rarely worth full implementation. Understand the concept; apply where it adds value.


Source & Credit

The Backend from First Principles series is based on what I learnt from Sriniously's YouTube playlist — a thoughtful, framework-agnostic walk through backend engineering. If this material helped you, please go check the original out: youtube.com/@Sriniously. The notes here are my own restatement for revisiting later.

⁂ Back to all modules