REST API Design

Representational State Transfer

"URLs are nouns, HTTP methods are verbs. Resources are identified by URLs, manipulated via HTTP methods, and transferred as representations — usually JSON."

CSE 135 — Full Overview | Review Questions

Section 1CRUD & REST

Mapping data operations to HTTP methods and URL patterns.

CRUD → HTTP Method Mapping

CRUDHTTP MethodCollection URLItem URL
CreatePOST/api/books
ReadGET/api/books/api/books/42
UpdatePUT / PATCH/api/books/42
DeleteDELETE/api/books/42
URLs are nouns, HTTP methods are verbs. POST=Create, GET=Read, PUT/PATCH=Update, DELETE=Delete. POST goes to the collection URL; PUT, PATCH, and DELETE go to the item URL.

Section 2URL Design

The RESTful spectrum — from good to bad.

The RESTful Spectrum

URLRESTfulnessWhy
GET /api/books/1RESTfulResource in URL, method implies action
GET /api/books?id=1Less RESTfulWorks, but ID should be in the path
GET /api?record=books&id=1Not RESTfulResource type hidden in query params
GET /api?id=1&action=deleteDefinitely not RESTAction in URL, wrong HTTP method
Avoid verb-based URLs. If you're writing /api/getBooks, you're putting the verb in the URL instead of using HTTP methods. The whole point of REST is that the HTTP method is the verb.

Section 3HTTP Status Codes

Communicate results through standard codes, not ad-hoc JSON.

Status Codes at a Glance

Success (2xx)

CodeWhen
200 OKGET, PUT, PATCH, DELETE success
201 CreatedPOST created a resource
204 No ContentDELETE with no body

Client Error (4xx)

CodeWhen
400Malformed / invalid input
401Not authenticated
403Authenticated, wrong permissions
404Resource doesn't exist
409Conflict (duplicate)
422Valid JSON, invalid semantics

CRUD → Status Code Mapping

OperationSuccessErrors
POST201400, 409
GET200404
PUT200404, 400
DELETE200/204404

Server Error (5xx)

CodeWhen
500Bug, DB down, unhandled error

Section 4PUT vs PATCH

Full replacement vs partial update — and idempotency.

PUT vs PATCH Comparison

AspectPUTPATCH
SemanticsFull replacement — send entire resourcePartial update — send only changed fields
Missing fieldsSet to defaults or removedLeft unchanged
Idempotent?Yes — same PUT twice = same resultNot guaranteed
ExamplePUT /api/books/42
{"title":"New","author":"X","year":2024}
PATCH /api/books/42
{"year":2025}
PUT replaces the entire resource. PATCH updates only specified fields. Many APIs use PUT for partial updates — technically wrong per spec, but extremely common. Know the difference.

Section 5Request & Response Formats

REST is format-agnostic — JSON dominates, but it's not the only option.

Input Formats & Content-Type

Content-TypeWhen Used
application/jsonJavaScript clients, API-to-API calls
application/x-www-form-urlencodedStandard HTML form submissions
multipart/form-dataFile uploads and mixed data

JSON encode/decode

LanguageEncodeDecode
JavaScriptJSON.stringify(obj)JSON.parse(str)
PHPjson_encode($arr)json_decode($str, true)
REST is format-agnostic. JSON dominates, but Accept and Content-Type headers enable format negotiation for HTML, CSV, XML, and more.

Content Negotiation: Multiple Output Formats

Accept HeaderUse Case
application/jsonJavaScript clients, SPAs, mobile apps
text/htmlServer-side rendering, HTMX, progressive enhancement
text/csvData exports, spreadsheets
application/xmlLegacy systems, enterprise, RSS/Atom
application/pdfGenerated reports, invoices
// Express — respond in the format the client wants app.get('/api/books', (req, res) => { const books = getBooks(); if (req.accepts('html')) res.type('html').send(renderTable(books)); else if (req.accepts('csv')) res.type('csv').send(toCSV(books)); else res.json(books); // JSON default });
Progressive enhancement: If your API accepts form-encoded data, a plain HTML form can submit data with zero JavaScript. Layer on JS for a richer experience, but the basic functionality works without it.

Section 6Richardson Maturity & HATEOAS

Four levels of REST maturity — and why most stop at Level 2.

Richardson Maturity Model

┌─────────────────────┐ │ Level 3 │ │ Hypermedia Controls│ ← HATEOAS: responses include links │ (HATEOAS) │ to related actions/resources ┌─┴─────────────────────┴─┐ │ Level 2 │ │ HTTP Verbs + Status │ ← GET/POST/PUT/DELETE + proper │ Codes │ status codes (200, 201, 404...) ┌─┴─────────────────────────┴─┐ │ Level 1 │ │ Individual Resources │ ← Different URLs per resource │ /users /books /orders │ (/users, /books, /orders) ┌─┴─────────────────────────────┴─┐ │ Level 0 │ │ "The Swamp of POX" │ ← One URL, POST everything │ POST /api (do everything here)│ (like XML-RPC or SOAP) └─────────────────────────────────┘

Level 3: HATEOAS Example

GET /api/books/42 { "id": 42, "title": "REST in Practice", "_links": { "self": { "href": "/api/books/42" }, "update": { "href": "/api/books/42", "method": "PUT" }, "delete": { "href": "/api/books/42", "method": "DELETE" }, "reviews": { "href": "/api/books/42/reviews" } } }
Most APIs target Level 2 — and that's generally sufficient. Level 3 (HATEOAS) is valuable for large, discoverable APIs like GitHub's, but overkill for internal apps where client and server are built by the same team.

Section 7Public vs Private APIs

Design considerations change based on who consumes your API.

Public vs Private API Comparison

AspectPrivate APIPublic API
ConsumersYour own front-end teamExternal developers worldwide
Breaking changesCoordinate & deploy togetherCan't — people depend on old behavior
VersioningOften unnecessaryEssential: /v1/api/books
DocumentationTeam Wiki, SlackPublished docs, OpenAPI spec
HATEOASRarely neededValuable for discoverability

Versioning Strategies

StrategyExampleNotes
URL path (most common)GET /v1/api/booksMost visible, widely used
HeaderAccept: application/vnd.myapi.v2+jsonCleaner URLs, harder to test
Query parameterGET /api/books?version=2Easy to add, less standard
The Bezos API Mandate: Design every API as if it might become public someday. It forces clean interfaces, versioning, and documentation from the start. The result of Amazon following this? AWS.

Section 8HTTP Method Challenges

Real-world complications with PUT, DELETE, and form submissions.

Problems & Workarounds

ProblemSolution
Corporate proxy strips PUT/DELETEUse HTTPS (proxy can't inspect encrypted traffic)
Firewall blocks methodsPOST with X-HTTP-Method-Override: PUT header
HTML forms only support GET/POSTJavaScript fetch() or hidden field _method=PUT
CORS preflight overheadHandle OPTIONS in your API
// Method override pattern // Client sends: POST /api/books/42 HTTP/1.1 X-HTTP-Method-Override: DELETE // Server checks: const method = req.headers['x-http-method-override'] || req.method;
In theory, REST maps cleanly to HTTP methods. In practice: proxies, firewalls, and HTML forms create friction. HTTPS solves the proxy problem; method override headers handle the rest.

Section 9CORS

Cross-Origin Resource Sharing — how browsers enforce access control.

Same-Origin Policy & CORS Flow

Same-Origin Policy: Browser (example.com) Server (api.other.com) ┌──────────────────┐ ┌──────────────────┐ │ JavaScript on │ fetch('/api/data') │ REST API │ │ example.com │ ──────────────────▶ │ │ │ │ ✗ BLOCKED │ │ │ │ ◀────────────────── │ │ └──────────────────┘ (no CORS headers) └──────────────────┘ With CORS: ┌──────────────────┐ ┌──────────────────┐ │ 1. Preflight │ OPTIONS /api/data │ Returns allowed │ │ (automatic) │ ──────────────────▶ │ origins/methods │ │ │ Access-Control-* │ │ │ │ ◀────────────────── │ │ │ 2. Real request │ GET /api/data │ Returns data + │ │ │ ──────────────────▶ │ CORS headers │ │ │ ◀────────────────── │ │ └──────────────────┘ └──────────────────┘
CORS is enforced by browsers, not servers. The server sends Access-Control-Allow-* headers to tell the browser what's permitted. Using * for Allow-Origin is dangerous in production — specify exact trusted origins instead.

Section 10API Tooling

Mocking, specification, and documentation tools.

json-server, OpenAPI & Swagger

json-server (Mocking)

  • Instant CRUD API from a JSON file
  • npx json-server db.json
  • Filtering, pagination, sorting
  • Great for prototyping, not for learning

OpenAPI (Specification)

  • Machine-readable API description (YAML/JSON)
  • Formerly called Swagger Specification
  • Enables auto-generated docs, SDKs, tests

Swagger Ecosystem

ToolPurpose
Swagger UIInteractive HTML docs
Swagger EditorBrowser editor + validation
CodegenGenerate client SDKs / server stubs

Design-First vs Code-First

  • Design-first: Write spec, then implement (parallel dev)
  • Code-first: Build API, generate spec from code
  • Design-first catches issues early but risks spec drift

Section 11Node.js vs PHP

Same REST API, different implementation details.

Side-by-Side Implementation

ConceptNode.js (Express)PHP
Get HTTP methodreq.method$_SERVER['REQUEST_METHOD']
URL parameterreq.params.id$_SERVER['PATH_INFO'] + parsing
Read JSON bodyreq.body (middleware)json_decode(file_get_contents('php://input'), true)
Send JSONres.json(data)echo json_encode($data)
Set statusres.status(201).json(data)http_response_code(201)
Routingapp.get('/api/items/:id', ...)Manual via PATH_INFO + switch
Data persistenceIn-memory (process stays running)File or DB (process restarts each request)
Execution modelPersistent processPer-request
The most important architectural difference: Node.js can store data in variables because the process runs continuously. PHP restarts on every request, so data must be stored externally (files, databases, sessions).

Section 12API Testing

APIs are contracts — testing verifies the contract holds.

Testing Tools & Checklist

Tools Comparison

ToolTypeBest For
curlCLIUniversal, CI pipelines
HTTPieCLIHuman-friendly syntax
PostmanGUITeam collaboration
DevToolsBrowserInspect live requests
supertestCodeAutomated testing (Node)

Testing Checklist

  • Happy path — correct input → expected output
  • Error cases — missing fields, invalid IDs
  • Status codes — 201 on create, 400 on bad input, 404 on missing
  • Response shape — correct JSON fields & types
  • Edge cases — empty collections, special characters
  • Auth — no token → 401, wrong perms → 403
  • Idempotency — safe to retry GET and PUT?

Section 13Beyond REST

When REST isn't enough — GraphQL, gRPC, and microservice pitfalls.

REST vs GraphQL vs gRPC

AspectRESTGraphQLgRPC
Data formatJSON (text)JSON (text)Protocol Buffers (binary)
EndpointsMany (/users, /posts)One (/graphql)Per service/method
Best forCRUD, public APIs, simple appsComplex UIs, mobile appsService-to-service, streaming
HTTP cachingWorks great (GET cacheable)Custom caching neededNot typically used
Browser supportNative (fetch, XHR)Native (just POST)Needs proxy (gRPC-Web)
Learning curveLowMediumHigher
DebuggingEasy (curl, DevTools)ModerateHarder (binary)
Start with REST. Move to GraphQL or gRPC when you have a specific problem REST doesn't solve well. Be skeptical of "we should use [X] because Netflix/Google uses it."

SummaryKey Takeaways

13 sections of REST API fundamentals in one table.

REST API Design at a Glance

ConceptKey Points
CRUD & RESTPOST=Create, GET=Read, PUT/PATCH=Update, DELETE=Delete. Collection URL vs item URL.
URL DesignNouns in URLs (/api/books/42), verbs in HTTP methods. Avoid /api/getBooks.
Status Codes200 OK, 201 Created, 204 No Content, 400/401/403/404 client errors, 500 server error.
PUT vs PATCHPUT replaces entire resource; PATCH updates specific fields. PUT is idempotent.
FormatsREST is format-agnostic. JSON dominates, but Accept/Content-Type enable negotiation.
Maturity ModelLevel 0 (one URL) → Level 1 (resources) → Level 2 (verbs) → Level 3 (HATEOAS).
Public vs PrivatePublic APIs need versioning, docs, stability. Design as if it might become public.
Method ChallengesProxies, firewalls, forms limit HTTP methods. HTTPS and method override solve most issues.
CORSBrowser-enforced; server sends Access-Control-Allow-* headers. Don't use * in production.
Toolingjson-server for prototyping, OpenAPI/Swagger for specs and docs.
Node.js vs PHPSame CRUD, different persistence: Node keeps state in memory, PHP needs external storage.
Testingcurl for CLI, Postman for GUI, supertest for automation. Test happy paths and error cases.
Beyond RESTGraphQL for flexible queries, gRPC for fast service-to-service. Start with REST.