SE

REST API design

Must-know concept2 hIntermediate

Resources, endpoints, CRUD mapping, versioning.

REST isn't a spec — it's a discipline: model the world as resources addressed by URLs, manipulate them with a uniform set of HTTP verbs, keep the server stateless. Do this consistently and your API will feel obvious to people who have never seen it.

The big idea

A good REST API answers two questions the same way every time: "what is the thing?" and "what am I doing to it?". The first becomes a noun in the URL (/orders/42), the second becomes an HTTP method (GET, POST, PUT, PATCH, DELETE). If a developer can guess the URL for a resource they have never seen and be right, you have done it well.

ListGET /orders
ReadGET /orders/42
CreatePOST /orders
UpdatePUT/PATCH /orders/42
DeleteDELETE /orders/42

Designing resources, not actions

Resources are nouns, never verbs. POST /createOrder is a smell. The interesting design work is choosing the right granularity and naming.

Do
GET    /users/42/orders
POST   /users/42/orders
DELETE /users/42/orders/9001

The resource is "orders belonging to user 42". Method picks the action.

Don't
GET  /getUserOrders?id=42
POST /createOrderForUser
POST /deleteOrder?id=9001

Verbs in the URL, IDs in query strings, POST doing everything.

CRUD ↔ HTTP mapping

The default mapping is unambiguous; deviate only when the protocol forces you.

AttributeOperationHTTP
Create onePOST /orders201 Created + Location header
Read collectionGET /orders?page=1200 OK + array
Read oneGET /orders/42200 OK + object · 404 if absent
Replace onePUT /orders/42200 / 204 — full body required
Patch onePATCH /orders/42200 / 204 — partial body
Delete oneDELETE /orders/42204 No Content

Pagination, filtering, sorting

Lists grow unboundedly. Pick one convention up front and use it everywhere:

GET /orders?status=paid&sort=-created_at&page=2&per_page=20

For high-volume APIs, prefer cursor pagination over offset (?cursor=eyJpZCI6OTAwMX0): offset gets slower as you go deeper and breaks on inserts.

Versioning

You will get the model wrong. Plan for it: pick one versioning scheme and never mix.

URL path
/v1/orders — cache-friendly, easy to debug. Default for most public APIs.
Header
Accept: application/vnd.api+json;version=2 — keeps URLs stable but hides the version.
Date
2025-11-01 — Stripe's approach; rarely worth its complexity outside that scale.

Error responses

Use the right status family, and put the structured error in the body. Be consistent.

{
  "error": {
    "code": "order.invalid_state",
    "message": "Cannot cancel an order that has already shipped.",
    "details": { "order_id": 9001, "current_state": "shipped" }
  }
}

In practice

Pick a stack with strong opinions (FastAPI, NestJS, .NET Minimal APIs) and let it enforce consistency — content negotiation, request validation, status codes, OpenAPI generation. The endpoints you hand-design will be a thin layer of routing on top.

Key takeaways

  • Resources are nouns; HTTP methods supply the verbs. No `getX` in URLs.
  • Map CRUD onto HTTP with the default conventions; deviate only with reason.
  • Standardise pagination, filtering, and sorting across every list endpoint.
  • Choose one versioning scheme up front — URL path is the safe default.
  • Errors use the right status code; the body adds machine-readable detail.

Checkpoint questions

Use these to test whether the lesson is clear enough to explain without rereading.

  1. 1Can you turn an action-shaped endpoint into a resource-shaped REST endpoint?
  2. 2Which HTTP method and status code would you use for create, read, update, and delete?
  3. 3When would cursor pagination be safer than offset pagination?
  4. 4What should a consistent API error response include?

References

External resources for going deeper after the lesson above.