REST constraints and resource modeling, versioning strategies, cursor vs offset pagination, OpenAPI 3.0 spec, Swagger UI — designing APIs that don't need a changelog.
Module P-10 — REST API Design and OpenAPI Documentation
What this module covers: A well-designed API is a product. Developers who consume it should be able to predict its behaviour, discover its capabilities, and upgrade to new versions without their code breaking. This module covers the REST constraints that produce predictable APIs, resource modelling, versioning strategies and their trade-offs, offset vs cursor pagination, the HTTP status codes that actually matter, and generating interactive OpenAPI 3.0 documentation from your Express routes with Swagger UI.
REST Constraints: What Makes an API RESTful
REST (Representational State Transfer) is not a standard — it is a set of architectural constraints defined by Roy Fielding. An API that follows them is predictable and easier to consume.
The six constraints, and what they mean in practice:
1. Uniform interface — a consistent way to interact with all resources. In practice: use HTTP methods semantically, use nouns not verbs in URLs, use standard status codes.
2. Stateless — each request contains all information needed to process it. No session state stored on the server between requests. Authentication via token (not session cookie) follows this constraint.
3. Client-server separation — the client and server evolve independently. Your mobile app and your API can be deployed separately.
4. Cacheable — responses must declare whether they can be cached. Use Cache-Control, ETag, and Last-Modified headers.
5. Layered system — the client doesn't know if it's talking to the actual server or a proxy/load balancer.
6. Code on demand (optional) — servers can send executable code to clients (e.g., JavaScript). Rarely used in APIs.
Most "REST APIs" only follow constraints 1–3. That's fine. What matters practically is the uniform interface.
Resource Modelling: URLs Are Nouns
URLs identify resources. HTTP methods express what to do with them.
Nested resources — when a resource only makes sense in the context of another:
Limit nesting depth — more than two levels becomes unwieldy. /users/42/posts/7/comments/3/likes is hard to read and hard to maintain. At that depth, consider a top-level resource: GET /comments/3/likes.
Actions that don't fit CRUD — some operations are not naturally resource-oriented. Use sub-resources with a verb:
These are acceptable when the alternative (overloading PATCH) would be unclear.
HTTP Methods and Idempotency
| Method | Idempotent | Safe | Use for |
|---|---|---|---|
| GET | ✓ | ✓ | Read — never modify state |
| HEAD | ✓ | ✓ | Like GET but no body — check existence/headers |
| POST | ✗ | ✗ | Create, or actions with side effects |
| PUT | ✓ | ✗ | Full replacement — same result if called multiple times |
| PATCH | ✗ | ✗ | Partial update |
| DELETE | ✓ | ✗ | Delete — second call returns 404, not an error |
Idempotent: calling the operation N times produces the same result as calling it once. Idempotency enables safe retries — crucial for unreliable networks. Design your API to support this: DELETE returning 404 on a second call is correct, not an error.
PATCH vs PUT:
For most APIs, PATCH is the right choice for updates — PUT requires the client to know the full current state.
HTTP Status Codes That Matter
Use the smallest set of status codes necessary. Consistency beats completeness.
A note on 400 vs 422: both are used for validation errors. 400 is more common, 422 is technically more correct for semantically invalid input that is syntactically valid. Pick one and be consistent — mixing them confuses clients.
API Versioning Strategies
APIs change. Versioning lets you make breaking changes without breaking existing clients.
Strategy 1: URL versioning — most common, most visible:
Pros: obvious, easy to test in browser, easy to route at the load balancer. Cons: URLs are supposed to identify resources, not API versions.
Strategy 2: Header versioning — cleaner URLs:
Pros: clean URLs. Cons: harder to test, often stripped by proxies.
Strategy 3: Content negotiation:
Accept: application/vnd.myapi.v2+json
Used by GitHub and Stripe. Most expressive, most complex.
The pragmatic choice: URL versioning (/v1/, /v2/) for public APIs. Header versioning for internal APIs. Start with /v1/ from day one — retrofitting versioning is painful.
What counts as a breaking change:
- Removing or renaming a field in a response
- Changing a field's type
- Removing an endpoint
- Changing authentication requirements
- Making an optional field required
What is NOT a breaking change:
- Adding new optional fields to responses
- Adding new optional query parameters
- Adding new endpoints
Pagination: Offset vs Cursor
Offset pagination — simple but has problems at scale:
Problems:
- Page drift: if an item is inserted while paginating, page 3 starts one record later — items get skipped or duplicated.
- Performance:
SELECT ... OFFSET 10000forces the database to scan and discard 10,000 rows.
Cursor pagination — solves both problems, harder to implement:
Cursor pagination is O(1) at any depth and is immune to page drift. Use it for feeds, activity streams, and any large dataset. Offset pagination is fine for admin UIs with small datasets where users need to jump to a specific page.
Filtering, Sorting, and Field Selection
Standard query parameter conventions:
OpenAPI 3.0 Documentation
OpenAPI (formerly Swagger) is the standard for describing REST APIs. A valid OpenAPI spec generates interactive documentation, client SDKs, and test mocks.
Defining the spec
Annotating routes with JSDoc
Serving Swagger UI
Visit http://localhost:3000/docs — interactive documentation where you can send real requests.
ETag Caching
ETags let clients avoid downloading unchanged resources:
Summary
- URLs are nouns, methods are verbs.
/users/42is the resource;DELETE /users/42is the operation. Verbs in URLs are a design smell. - Limit nesting to two levels. Deeper nesting is hard to maintain. Prefer top-level resources with filter params over deep nesting.
- PATCH for partial updates, PUT for full replacement. PATCH is almost always what you want.
- Idempotency matters for reliability. GET, DELETE, and PUT should be safely retryable. Design PATCH with idempotency in mind.
- URL versioning (
/v1/,/v2/) is the pragmatic choice. Start with/v1/on day one. - Cursor pagination for large datasets and feeds — no page drift, O(1) query at any depth. Offset pagination for small admin datasets where jumping to page N matters.
- OpenAPI 3.0 spec + Swagger UI = interactive docs your API consumers can use immediately. Generate from JSDoc comments in route files — documentation stays co-located with the code.
Next: GraphQL with Apollo Server — schema-first API design, queries, mutations, subscriptions, the N+1 problem and how DataLoader solves it, and when GraphQL is the better choice over REST.
When designing a REST API, what is the key difference between the HTTP PUT and PATCH methods?
Why is cursor-based pagination generally preferred over offset-based pagination for large datasets or real-time feeds?
Which of the following is considered a breaking change when versioning an API?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
Sign in & RegisterDiscussion
0Join the discussion