|$ curl https://forge-ai.dev/api/markdown?path=docs/backend/rest
$cat docs/rest-api-design.md
updated Recently·26 min read·published

REST API Design

BackendRESTIntermediate🎯Free Tools
Introduction

Representational State Transfer, or REST, is the dominant architectural style for designing networked APIs. It models interactions as stateless transfers of resource representations between clients and servers. When designed well, REST APIs are predictable, cacheable, and easy to evolve.

REST is not a protocol or a standard. It is a set of constraints that, when applied to HTTP, produce systems that scale and compose. The core ideas are resources identified by URLs, a uniform interface of HTTP methods, stateless request processing, and representations negotiated through media types.

This guide covers the practical decisions you will face when building a REST API: naming resources, choosing HTTP methods, returning correct status codes, versioning safely, linking related resources with HATEOAS, documenting with OpenAPI, and avoiding the anti-patterns that make APIs hard to consume.

Resources and URL Naming

In REST, URLs identify resources, not actions. A resource is any noun that is meaningful to your domain: users, orders, invoices, comments. Use plural nouns for collections and path segments for hierarchy. Avoid verbs in URLs because HTTP methods already express actions.

Consistency matters more than perfection. Pick a convention and apply it everywhere. Use kebab-case for multi-word names. Keep URLs shallow when possible. Deeply nested resources become hard to read and can force consumers to know relationships they should not care about.

AvoidPreferReason
/getUsers/usersHTTP method implies the action
/users/123/posts/createPOST /users/123/postsPOST on collection creates a member
/user/123/users/123Plural nouns for collections
/orders?status=archiveGET /orders?status=archivedUse adjectives for filter values
rest-routes.ts
TypeScript
1// Express router with RESTful resource routes
2import { Router } from "express";
3
4const usersRouter = Router();
5
6usersRouter.get("/", listUsers); // collection read
7usersRouter.post("/", createUser); // collection create
8usersRouter.get("/:id", getUser); // member read
9usersRouter.patch("/:id", updateUser); // member partial update
10usersRouter.put("/:id", replaceUser); // member full replacement
11usersRouter.delete("/:id", deleteUser); // member delete
12
13usersRouter.get("/:id/orders", listUserOrders); // sub-resource collection
14usersRouter.post("/:id/orders", createUserOrder);
15
16export default usersRouter;

info

Prefer shallow nesting. Instead of /users/123/orders/456/items/789, expose /orders/456 and link to related resources. Deep nesting couples clients to your internal hierarchy.
HTTP Methods and Idempotency

HTTP provides a small vocabulary of methods, each with clear semantics. Using them consistently makes your API self-documenting and lets intermediaries such as caches and proxies behave correctly.

MethodActionIdempotentSafe
GETRetrieve a resourceYesYes
POSTCreate or trigger actionNoNo
PUTFull replacementYesNo
PATCHPartial updateUsuallyNo
DELETERemove a resourceYesNo
HEADRetrieve headers onlyYesYes
OPTIONSDescribe capabilitiesYesYes

Idempotency is one of the most important properties for reliable APIs. A request is idempotent if repeating it produces the same server state as making it once. GET, PUT, DELETE, and OPTIONS are idempotent. POST is not because creating a resource twice usually creates two resources.

warning

PATCH idempotency depends on your implementation. A JSON Patch document that sets name to a fixed value is idempotent. A PATCH that increments a counter is not. Document and test the behavior of your partial updates.
Status Codes

HTTP status codes communicate the outcome of a request. Using the right code lets clients decide whether to retry, redirect, or surface an error. Do not invent custom status codes for conditions that existing codes already cover.

CodeMeaningWhen to Use
200 OKSuccessSuccessful GET, PUT, PATCH, DELETE
201 CreatedResource createdSuccessful POST that creates a resource
204 No ContentSuccess with empty bodyDELETE or actions with no response
400 Bad RequestClient errorValidation failures, malformed JSON
401 UnauthorizedAuthentication requiredMissing or invalid credentials
403 ForbiddenNot allowedAuthenticated but lacks permission
404 Not FoundResource missingUnknown URL or deleted resource
409 ConflictState conflictDuplicate email, stale optimistic lock
422 Unprocessable EntitySemantic errorValid syntax but business rule violation
429 Too Many RequestsRate limitedClient exceeded rate limit
500 Internal Server ErrorServer failureUnexpected exception
503 Service UnavailableTemporarily downMaintenance, overload, upstream outage

best practice

Return 401 only when credentials are missing or invalid. Return 403 when the user is authenticated but not authorized. The distinction matters for client error handling and audit logging.
Request and Response Formats

Consistency in request and response bodies reduces friction for API consumers. Use JSON for most APIs unless binary performance is critical. Wrap collection responses in an object so you can add pagination, metadata, and links without breaking clients.

collection-response.json
JSON
1{
2 "data": [
3 {
4 "id": "usr_123",
5 "email": "alice@example.com",
6 "name": "Alice Chen",
7 "createdAt": "2026-01-15T08:23:00Z",
8 "links": {
9 "self": "/users/usr_123",
10 "orders": "/users/usr_123/orders"
11 }
12 }
13 ],
14 "meta": {
15 "total": 42,
16 "page": 1,
17 "perPage": 20
18 },
19 "links": {
20 "self": "/users?page=1",
21 "next": "/users?page=2",
22 "last": "/users?page=3"
23 }
24}

Accept and return dates in ISO 8601 UTC format. Use snake_case or camelCase consistently for JSON keys. If your API is public, pick snake_case because it is the most common convention across languages and libraries.

info

Always return an object at the top level of the response. Returning a raw array makes future expansion painful because you cannot add metadata without breaking clients.
API Versioning

APIs evolve. New fields are added, behavior changes, and old endpoints are deprecated. Versioning lets you introduce breaking changes without forcing every client to update simultaneously. The three most common strategies are URL path versioning, header versioning, and content negotiation.

StrategyExampleProsCons
URL path/v1/usersExplicit, easy to cacheClutters URLs
HeaderX-API-Version: 2025-01-01Clean URLsHarder to test in browser
Content typeAccept: application/vnd.api+json;v=2Strict HTTP semanticsVerbose, poor tooling support

URL path versioning is the safest default for most teams. It is visible, works with every HTTP client, and is easy to route in load balancers and CDNs. Reserve header or content-type versioning for APIs where URL cleanliness is critical.

warning

Do not version unless you have a breaking change. Additive changes, such as new optional fields or new endpoints, do not require a new version. Maintain old versions long enough for clients to migrate.
HATEOAS and Hypermedia

Hypermedia as the Engine of Application State, or HATEOAS, is the REST constraint that says clients should navigate the API by following links provided in responses, not by hard-coding URLs. This reduces coupling between clients and servers because the server controls available actions.

hateoas-response.json
JSON
1{
2 "data": {
3 "id": "ord_456",
4 "status": "pending",
5 "total": 199.99,
6 "links": {
7 "self": { "href": "/v1/orders/ord_456" },
8 "customer": { "href": "/v1/users/usr_123" },
9 "items": { "href": "/v1/orders/ord_456/items" }
10 },
11 "actions": [
12 {
13 "rel": "cancel",
14 "method": "POST",
15 "href": "/v1/orders/ord_456/cancel",
16 "condition": "status === 'pending'"
17 },
18 {
19 "rel": "pay",
20 "method": "POST",
21 "href": "/v1/orders/ord_456/payments",
22 "condition": "status === 'pending'"
23 }
24 ]
25 }
26}

Full HATEOAS is rare in modern APIs because it adds payload size and client complexity. A pragmatic compromise is to include a small set of stable links, such as self, next, prev, and related. This gives clients useful navigation without requiring a hypermedia client library.

📝

note

If you do adopt HATEOAS, make link relations stable contracts. Changing a rel value is a breaking change for hypermedia-aware clients.
OpenAPI and Documentation

OpenAPI is the industry standard for describing REST APIs. An accurate OpenAPI document becomes the source of truth for documentation, client SDK generation, mock servers, and contract tests. Generate it from code annotations or TypeScript types to keep it in sync with the implementation.

openapi-spec.yaml
YAML
1openapi: 3.0.3
2info:
3 title: ForgeLearn Orders API
4 version: 1.0.0
5paths:
6 /users:
7 get:
8 summary: List users
9 parameters:
10 - name: page
11 in: query
12 schema:
13 type: integer
14 default: 1
15 responses:
16 "200":
17 description: A paginated list of users
18 content:
19 application/json:
20 schema:
21 type: object
22 properties:
23 data:
24 type: array
25 items:
26 $ref: "#/components/schemas/User"
27 meta:
28 $ref: "#/components/schemas/PaginationMeta"
29components:
30 schemas:
31 User:
32 type: object
33 properties:
34 id:
35 type: string
36 email:
37 type: string
38 format: email
39 required: [id, email]

best practice

Publish your OpenAPI document at a stable URL such as /openapi.json or /api/docs. Tools like Swagger UI, Redoc, and Scalar can render it automatically.
Common Anti-Patterns

Even experienced teams fall into recurring traps. Recognizing them early saves refactoring later.

Anti-PatternProblemSolution
200 with error bodyClients cannot detect failuresUse proper 4xx/5xx codes
POST for everythingLoses caching and semanticsUse GET, PUT, PATCH, DELETE
Returning database rowsLeaks schema and internalsMap to DTOs / view models
No paginationUnbounded list endpointsDefault page size, max limit
Inconsistent namingCognitive load for consumersAdopt and enforce a style guide
Exposing stack tracesSecurity riskReturn generic messages, log details

danger

Never expose internal stack traces, database error messages, or secret configuration values in API responses. These leaks make exploitation easier and violate the principle of least disclosure.
$Blueprint — Engineering Documentation·Section ID: BE-01·Revision: 1.0