REST API design done right

By Shashank Sharma Checklist 4 min read Published August 2026

A REST API is a contract other people build on — so mistakes are expensive to change later. Run this checklist before you ship a new endpoint: it covers the decisions clients feel first, from naming to errors to auth.

Resource naming & structure

  • Use nouns, not verbs. GET /orders, not /getOrders. The HTTP method is the verb.
  • Plural, consistent collections. /users, /users/42, /users/42/orders.
  • Keep nesting shallow — one or two levels; don’t model your whole schema in the URL.
  • Match methods to intent — GET reads, POST creates, PUT/PATCH updates, DELETE removes. GET is safe and idempotent.
# collection and member routes
GET    /users            list users
POST   /users            create a user
GET    /users/42         fetch one
PATCH  /users/42         partial update
DELETE /users/42         remove
GET    /users/42/orders  that user's orders

When an operation genuinely isn’t CRUD — sending an email, kicking off a job — model it as a sub-resource you create (POST /reports) rather than an RPC-style verb like /generateReport. The URL stays a noun; the method carries the action.

Status codes

  • 2xx for success — 200 OK, 201 Created (with a Location), 204 No Content.
  • 4xx for client errors — 400 bad input, 401 unauthenticated, 403 forbidden, 404 not found, 409 conflict, 422 validation.
  • 5xx for server errors — never return 200 with an error body.

Error shapes

  • One consistent error format across every endpoint.
  • Machine-readable code + human message — e.g. { "code": "INVALID_EMAIL", "message": "..." }.
  • Field-level detail for validation errors so clients can highlight the right input.
  • Never leak internals — no stack traces or SQL in responses.
// 422 Unprocessable Entity — the same shape on every endpoint
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Some fields are invalid.",
    "fields": [
      { "field": "email", "code": "INVALID_EMAIL", "message": "Enter a valid email." },
      { "field": "age",   "code": "OUT_OF_RANGE",  "message": "Must be 18 or older." }
    ],
    "requestId": "req_8f3ac21"
  }
}

A stable envelope like this lets clients write one error handler instead of special-casing each endpoint. Include a requestId that also appears in your logs — it turns a vague user report into the exact trace, and it’s the one field worth returning even on 5xx responses.

Practice this
Build real APIs in Node.js
16 challenges on routing, validation, responses and middleware, each checked by a test suite.
Start practicing

Pagination & filtering

  • Paginate list endpoints — cursor-based for large or changing datasets, offset for simple cases.
  • Return page metadata — total count and/or next cursor.
  • Standardize filtering & sorting via query params, and document allowed fields.
GET /orders?limit=20&cursor=eyJpZCI6MTIzfQ

{
  "data": [ /* up to 20 orders */ ],
  "page": { "nextCursor": "eyJpZCI6MTQzfQ", "hasMore": true }
}

Pick sane limits — a default page size around 20 and a hard maximum near 100, so no client can ask for a million rows in one call. Cursor pagination stays correct when rows are inserted or deleted mid-scroll; offset pagination (?page=3) is simpler but skips or repeats rows on data that changes between requests.

Versioning

  • Version from day one — /v1/... is simple and cache-friendly.
  • Only break on a new version — additive changes stay within the current one.
  • Give clients a migration window before retiring an old version.

Auth & security

  • HTTPS only, always.
  • Authenticate every non-public endpoint and enforce authorization per resource.
  • Scope tokens to least privilege; keep them short-lived.
  • Set CORS deliberately — don’t reflect arbitrary origins.
Authorization: Bearer <access_token>

// pair a short-lived access token with a revocable refresh token
access_token   expires in ~15 minutes
refresh_token  expires in ~7-30 days, stored securely, revocable

Short access-token lifetimes limit the damage a leaked token can do; the refresh token keeps users signed in without sending long-lived credentials on every request. On a 401 a client refreshes once and retries — on a 403 it doesn’t, because the user is authenticated but simply not allowed to touch that resource.

Validation & limits

  • Validate and sanitize all input at the boundary; reject unknown fields.
  • Rate-limit public and auth endpoints to prevent abuse.
  • Bound payload sizes and paginate to avoid unbounded responses.
  • Make writes idempotent where possible (idempotency keys for POST).

Pre-ship checklist

  • Nouns, plural, shallow nesting
  • Correct status code for every path
  • One consistent error shape, no leaked internals
  • List endpoints paginated with metadata
  • Versioned and documented
  • Auth, CORS, rate limits, and input validation in place

FAQ

Should API endpoints use nouns or verbs?

Nouns. Resources are things, and the HTTP method is the verb — GET /orders, POST /orders, DELETE /orders/42. Reserve verb-style endpoints for genuine actions that don't map to a resource.

How should I version a REST API?

Pick one scheme and apply it consistently. URL versioning (/v1/...) is the most common and cache-friendly. Version when you make breaking changes, and keep the old version alive long enough for clients to migrate.

What does a good API error look like?

A consistent JSON shape with a machine-readable code, a human-readable message, and enough detail to act on — returned with the correct HTTP status. Every endpoint should use the same error format.

Put it into practice

Solve real challenges in a full browser IDE — graded automatically.

Open the learning path

We use cookies that are necessary to run Staffcoder, and optional analytics cookies to see which pages get used. Analytics stays off unless you accept. Read our Cookie Policy.