Skip to content

REST API Design ​

REST API design turns resources and HTTP behavior into a predictable contract for clients. A URL should be designed together with resource identity, status codes, error shape, caching, and evolution policy rather than treated as only a controller path.

Quick Decision ​

NeedStarting decisionWatch out
Resource collectionPlural URI (/users)Avoid verb-based URIs
Single-resource operationHTTP method plus resource IDDocument idempotency behavior
Large listCursor or keyset paginationKeep the ordering key stable
Complex searchQuery parameters or a separate search endpointDo not accept free-form SQL
Partial updatePATCH with explicit merge semanticsDistinguish null from field removal

Production Checklist ​

  • Problem: Are resources, sub-resources, and aggregate boundaries clear?
  • Solution: Are method, status code, error body, pagination, and filter contracts defined?
  • Trade-off: REST provides simplicity and HTTP tooling; complex screens may need a BFF or GraphQL.
  • Failure mode: Validation, authorization, conflict, timeout, and rate-limit responses must be distinct.
  • Measurement: Track p95 latency, payload size, page depth, 4xx/5xx rates, and cache hits.
  • Security/cost: Restrict filter fields with an allowlist; protect large responses and expensive queries with quotas.

Resource Modeling ​

  • Use /orders for a collection and /orders/{orderId} for one resource.
  • Use /orders/{id}/items when the relationship is part of the resource lifecycle; use /items?orderId={id} when items are independently queried.
  • Do not put verbs such as create, get, or delete in URIs; use an explicit action endpoint only when the domain command cannot be represented as a resource.
  • Standardize ID formats and time representations. Carry resource metadata and contract version when it is useful to clients.

HTTP Methods and Status Codes ​

OperationMethodSuccessImportant error
ListGET /orders200 OK400, 401, 403
Single resourceGET /orders/{id}200 OK404 Not Found
CreatePOST /orders201 Created + Location409, 422
Full updatePUT /orders/{id}200 or 204404, 409
Partial updatePATCH /orders/{id}200 or 204400, 409
DeleteDELETE /orders/{id}204 No Content404, 409

202 Accepted means that work was queued; 429 Too Many Requests means the client should try later; 503 Service Unavailable represents temporary capacity or dependency trouble. Use one error shape containing status, stable error code, message, field errors, request/correlation ID, and retry information when applicable.

Pagination, Filtering, and Sorting ​

Pagination choices ​

ModelGood forCost
Offset (page, size)Small and mostly stable listsSlow deep pages and shifts during writes
CursorChanging feeds and infinite scrollCursor must be opaque and have a lifetime
KeysetLarge tables with indexed orderingClients cannot page by every field

Define stable sort, limit, nextCursor, or hasMore semantics for every list. Cap limit; document the default ordering and tie-breaker (createdAt, id).

Filtering and sorting ​

Use allowlisted query parameters such as status=active, createdAfter=..., and sort=-createdAt,name. Do not expose arbitrary field names or directions. Measure query plans, index requirements, and timeout budgets for filter combinations. If a search language is required, define a constrained search contract instead of passing SQL or ORM expressions to the API.

Bulk Operations and HATEOAS ​

Bulk endpoints must define per-item results, error codes, and idempotency behavior. Use one transaction only when all items share a safe consistency boundary; return 202 and a job-status resource for long bulk work.

HATEOAS means more than adding URLs: it allows a client to discover valid next actions.

json
{
  "id": "ord_123",
  "status": "pending",
  "_links": {
    "self": { "href": "/api/orders/ord_123" },
    "cancel": { "href": "/api/orders/ord_123/cancellation", "method": "POST" }
  }
}

Link relation names must match authorization and current state. A link never replaces a server-side security check.

OpenAPI and API Performance ​

An OpenAPI contract defines paths, parameters, request/response schemas, status codes, security schemes, and examples. SpringDoc-generated documentation, hand-written descriptions, and generated clients should come from the same contract. See API Contracts and Tooling for the workflow.

Keep payloads small; use field selection, compression, ETags/conditional requests, and response caching where appropriate. N+1 queries, expensive sorting, and unlimited nested expansion are backend bottlenecks that are invisible at the HTTP layer.

Outcome ​

A good REST design makes successful and failed flows repeatable for the same client. For every endpoint, document an example request/response, error contract, auth scope, retry/idempotency behavior, and pagination rules.

Created by Eren Demir.