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:
- Client-Server — Separation of concerns. UI from data storage.
- Stateless — Each request contains all info needed. No session state on server.
- Cacheable — Responses declare if they're cacheable.
- Uniform Interface — Resources identified by URIs, manipulated via representations.
- Layered System — Client can't tell if it's talking to the real server or a proxy.
- 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.
Bad:
GET /getUsers
POST /createUser
POST /deleteUser/123
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
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.
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.
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.
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.
Success (single resource):
{ "data": { "id": "123", "name": "Alice", "email": "alice@example.com" } }
Success (list):
{
"data": [...],
"meta": { "total": 100, "page": 1, "limit": 20 }
}
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:
{
"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.
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.