Handbooks / REST API Design / Chapter 2

Resource Design

7 pages · ~5 min✓ Reviewed

Builds on HTTP Methods and Status Codes. Next up: Authentication and Authorization.

REST API DesignPage 1

REST Mental Model and Constraints

1. Resources

  • Everything is a resource (a noun) at a URI
  • Clients exchange representations (usually JSON) of its state

2. Six Constraints

  • Client-server: UI and data split
  • Stateless: each request carries all context (token, params)
  • Cacheable: responses declare it
  • Uniform interface: standard verbs and URIs
  • Layered system: proxies, gateways
  • Code-on-demand: optional
Stateless

No server session between calls: any server can answer, so you scale horizontally.

3. Methods

MethodUseSafeIdempotent
GETreadyesyes
POSTcreatenono
PUTreplacenoyes
PATCHpartial updatenonot guaranteed
DELETEremovenoyes
  • Of these five, only GET is safe (HEAD and OPTIONS are safe too)

4. Interview Point

  • RESTful vs just HTTP? Resources plus all six constraints
  • Why stateless? Any server serves any request, so it scales out
  • Safe vs idempotent? Safe: GET. Idempotent: GET, PUT, DELETE
REST API DesignPage 2

Resource Naming, Nesting and Status Codes

1. Naming Rules

  • Plural nouns, lowercase kebab-case
  • Never verbs: the HTTP method is the verb
AvoidUse
/getOrder?id=42GET /orders/42
/createUserPOST /users
/OrderItems/order-items

2. Nesting

  • Nest only to show ownership, 1-2 levels deep
  • Item has a unique ID? Expose it at top level: /orders/42
  • Keep the nested path for listing: /users/7/orders
PathVerdict
/users/7/ordersOK, ownership
/users/7/orders/42/items/3/reviewsToo deep
/items/3/reviewsFlattened

3. Actions and Create

  • Non-CRUD action: model a noun sub-resource, not a verb
  • Cancel an order: POST /orders/42/cancellation
http
POST /orders
HTTP/1.1 201 Created
Location: /orders/43
Create response

POST returns 201 Created plus a Location header pointing at the new resource.

REST API DesignPage 3

Filtering, Sorting, Bodies and Error Formats

1. Query Params on the Collection

http
GET /orders?status=paid&sort=-created_at&limit=20&cursor=abc

a - prefix means descending

  • Filter, sort and paginate with query params
  • Cursor pagination for large or changing data
  • Offset slows down and skips or duplicates rows as data shifts

2. Bodies and Status Codes

  • Send JSON with Content-Type: application/json
  • PUT needs the full object; PATCH sends only changed fields
  • Return the created or updated resource
CodeMeaning
200 / 201 / 204OK / Created / No Content
400 / 422bad input / validation failed
401 / 403unauthenticated / forbidden
404 / 409not found / conflict
429 / 500rate limited / server error

3. Error Body

json
{ "type": "...",
  "title": "Validation failed",
  "status": 422,
  "detail": "email is invalid",
  "errors": [{ "field": "email" }] }

RFC 9457 problem+json shape

Same shape every time

401 = not logged in. 403 = logged in but not allowed.

REST API DesignPage 4

HATEOAS and REST vs GraphQL vs gRPC

1. HATEOAS

  • Responses carry links to the next valid actions
  • Clients follow links instead of hardcoding URLs
  • Most real APIs skip it
json
{ "id": 42, "status": "pending",
  "_links": {
    "pay": "/orders/42/payment",
    "cancel": "/orders/42" } }

the server decides which actions are open

2. REST vs GraphQL vs gRPC

RESTGraphQLgRPC
Shapemany URLs, HTTP + JSONone endpoint, client picks fieldsProtobuf over HTTP/2, strict contract
Best forpublic APIsvaried clients, e.g. mobileinternal service-to-service
Strengtheasy HTTP cachingfixes over-fetchingstreaming, small payloads
Riskover- and under-fetchingharder caching; needs depth or complexity limitsbrowsers need gRPC-Web; hard to debug by eye

3. Interview Point

  • What does HATEOAS solve? Hardcoded URLs: links tell clients the next valid actions
  • GraphQL over REST? Varied clients that need exact fields
  • gRPC over REST? Fast internal calls with a strict contract
  • Why is GraphQL caching harder? One endpoint, so URL-keyed HTTP caches cannot tell queries apart
REST API DesignPage 5

Common REST Design Mistakes

1. Mistakes and Fixes

MistakeFix
POST /createUserPOST /users
GET /deleteOrder?id=42DELETE /orders/42
200 + error in body4xx + problem+json
500 for bad input400 / 422
Unbounded GET /orders/v1/orders?limit=20
  • GET that changes state breaks the safe contract
  • Crawlers and prefetchers fire it: side effects
  • 200 + error hides failures from clients and monitoring

2. Version and Paginate Early

  • Add /v1 or a version header from day one
  • Put a limit on every collection, with a max cap
  • Unbounded lists break later: slow, huge payloads
http
GET /v1/orders?limit=20&cursor=abc

version + limit + cursor

3. Summary and Interview Points

Status code is the contract

Noun URL + right method + honest status: 4xx = client fault, 5xx = server fault.

  • Version a public API? /v1 in path or a header; keep old versions alive
  • No pagination? Huge responses, timeouts, memory pressure as data grows
  • Errors as 200? Clients, caches and monitoring read only the status code
REST API DesignPage 6

REST API Cheat Sheet

1. Methods and URLs

MethodUseIdempotent
GETreadyes
POSTcreateno
PUTreplaceyes
PATCHpartial updatenot guaranteed
DELETEremoveyes
  • Plural nouns, kebab-case, nest at most two levels
  • Filter, sort and paginate with query params
  • - prefix on sort means descending
http
GET /orders?status=paid&sort=-created_at&limit=20&cursor=...

2. Status Codes

GroupCodes
Success200 OK, 201 + Location, 204 No Content
Client400 input, 401 unauthenticated, 403 forbidden, 404, 409 conflict, 422 validation, 429 rate limited
Server500

3. Pick One

  • REST: public CRUD APIs
  • GraphQL: flexible client-driven queries
  • gRPC: fast internal service calls
  • HATEOAS is optional
Errors

Never return 200 with an error body. Keep 500 for server faults only.

REST API DesignPage 7

Check yourself

A user has name and email. A client sends PUT /users/7 with only {"email": "new@x.com"}. What goes wrong?
  • PUT replaces the whole object, so name is lost
  • Use PATCH to send only changed fields
Page 1 of /feed?offset=0&limit=20 is loaded. A new row is then inserted at the top. What does page 2 show?
  • The old 20th row shifts to 21st, so it appears twice
  • Cursor pagination avoids this
Spot the bug: GET /orders/42/cancel cancels an order. A link prefetcher visits it. Result?
  • The order is cancelled, because GET must be safe
  • Use POST /orders/42/cancellation