REST API Design Guide
A REST API is a contract with people you will never meet. Boring consistency — predictable URLs, honest status codes, uniform errors — is worth more than any clever abstraction.
Design for evolution from day one: version the surface, paginate every list, and describe everything in an OpenAPI spec so documentation and typed clients generate themselves instead of drifting out of date.
Resources, methods, status codes
Use plural nouns (/users/42/orders) and keep nesting one level deep. Let methods mean what HTTP says: GET is safe, PUT replaces idempotently, PATCH edits partially, POST creates or triggers. Return 201 with a Location header on create, 204 on delete, 400 for malformed requests versus 422 for valid-but-unprocessable ones, 401 for who-are-you versus 403 for not-allowed, 409 for conflicts, and 429 with Retry-After when throttling.
Pagination and filtering
Offset pagination (?page=3) is simple but skews when rows are inserted mid-scroll and slows on deep pages. Cursor pagination returns an opaque next_cursor token and stays stable and fast at any depth — the right choice for feeds and large tables. Cap page sizes server-side, and expose filtering and sorting as explicit, validated query parameters rather than raw field pass-through.
Errors and versioning
Adopt RFC 9457 problem+json so every error carries type, title, status and detail, plus a machine-readable application code clients can branch on. Version in the path (/v1/) — visible, cacheable, easy to route. Additive changes like new optional fields need no bump; renames and removals wait for v2, announced with deprecation and sunset headers.
Idempotency and documentation
Accept an Idempotency-Key header on unsafe POSTs — the Stripe pattern — storing the first response and replaying it on retries so an order or charge can never double-create. Treat your OpenAPI spec as the source of truth: validate requests against it at runtime, render docs with Swagger UI, Redoc or Scalar, and generate typed clients in CI.
Frequently asked questions
Should I use PUT or PATCH for updates?
PUT replaces the whole resource idempotently; PATCH applies a partial change. Most product APIs mainly need PATCH — just define semantics for null versus omitted fields.
When should I bump to /v2?
Only on breaking changes: removing or renaming fields, changing types or semantics. Adding optional fields is safe within v1.
Cursor or offset pagination?
Cursor for anything user-facing or large — it stays stable under inserts and fast at depth. Offset is fine for small admin tables.
Is REST still the right choice over GraphQL?
For public and partner APIs, usually yes — cacheable, tool-rich, easy to secure. GraphQL and tRPC shine for first-party frontends with fast-moving data needs.