API Design Patterns

Request formats, versioning, testing, documentation, and what lies beyond REST

Request and Response Formats

REST is explicitly format-agnostic — the architecture says nothing about JSON, XML, or any specific data format. Instead, it relies on the Content-Type header (what format the body is in) and the Accept header (what format the client wants back) to negotiate representations.

Beyond JSON: Multiple Input Formats

If your API only accepts JSON request bodies, you've excluded standard HTML <form> elements — which means every form needs JavaScript just to submit data. Real-world APIs often handle multiple input formats:

Content-Type When It's Used
application/json JavaScript clients, API-to-API calls
application/x-www-form-urlencoded Standard HTML form submissions (the default for <form>)
multipart/form-data File uploads and mixed data

Content Negotiation: Multiple Output Formats

The "Representational" in REST means a resource can have multiple representations. The Accept header is how the client requests a specific format:

Accept Header Use Case
application/json JavaScript clients, SPAs, mobile apps
text/html Server-side rendering, HTMX, progressive enhancement
text/csv Data exports, spreadsheet-friendly downloads
application/xml Legacy systems, enterprise integrations

Public vs Private APIs

The design considerations change dramatically based on who's consuming your API:

Aspect Private API Public API
Consumers Your own front-end team External developers worldwide
Breaking changes Coordinate with your team You can't — people depend on the old behavior
Versioning Often unnecessary Essential: /v1/api/books, /v2/api/books
Documentation Team Wiki, Slack Published docs, OpenAPI spec, changelogs
HATEOAS Rarely needed Valuable for discoverability

Versioning Strategies

# URL path versioning (most common) GET /v1/api/books GET /v2/api/books # Header versioning GET /api/books Accept: application/vnd.myapi.v2+json # Query parameter versioning GET /api/books?version=2

URL path versioning is the most visible and widely used approach. Once published, don't change existing behavior — add new versions instead.

HTTP Method Challenges

In theory, REST cleanly maps to HTTP methods. In practice, there are complications:

  • Corporate proxies may strip or block PUT and DELETE requests
  • Firewalls sometimes only allow GET and POST
  • HTML forms only support GET and POST natively
  • CORS preflight adds an OPTIONS request before non-simple methods
Problem Solution
Proxy strips PUT/DELETE Use HTTPS (proxies can't inspect encrypted traffic)
Firewall blocks methods POST with X-HTTP-Method-Override: PUT header
HTML form limitation Use JavaScript fetch() or hidden field _method=PUT
CORS preflight overhead Handle OPTIONS in your API (return allowed methods)

API Mocking and json-server

You don't always need to build a backend before building a frontend. API mocking lets you prototype quickly. json-server generates a full CRUD REST API from a single JSON file:

npx json-server db.json

Given a db.json with {"books": [...]}, json-server automatically creates GET/POST/PUT/PATCH/DELETE endpoints at /books and /books/:id. It even supports filtering, pagination, and sorting. Changes persist back to the JSON file.

OpenAPI (Swagger)

The OpenAPI Specification (OAS) is a standard, language-agnostic format for describing REST APIs. Because it's machine-readable, tools can automatically generate documentation, client SDKs, server stubs, and tests from it.

openapi: 3.0.3 info: title: Books API version: 1.0.0 paths: /api/books: get: summary: List all books responses: '200': description: Array of books content: application/json: schema: type: array items: $ref: '#/components/schemas/Book' components: schemas: Book: type: object properties: id: type: integer title: type: string author: type: string
Tool What It Does
Swagger UI Renders your spec as interactive HTML documentation
Swagger Editor Browser-based editor with real-time validation and preview
OpenAPI Generator Generate client SDKs and server stubs from your spec

API Testing

APIs are contracts — they promise that given certain inputs, they'll produce certain outputs. Testing verifies that the contract holds.

Testing Tools

Tool Type Best For
curl CLI Quick testing, CI pipelines, everywhere
HTTPie CLI Human-friendly syntax, colored output
Postman / Insomnia GUI Exploring APIs, team collaboration, saved collections
Browser DevTools Built-in Inspecting live traffic, "Copy as curl"
supertest (Node.js) Automated CI/CD, regression testing, test suites

What to Test

  • Happy path — correct input produces expected output
  • Error cases — missing fields, invalid IDs, wrong data types
  • Status codes — verify 201 on create, 400 on bad input, 404 on missing
  • Response shape — correct JSON structure, expected fields present
  • Idempotency — can you safely retry the same request? (GET and PUT should be safe)

Beyond REST

REST is the dominant architectural style, but it's not perfect for every situation:

Problem Why REST Struggles
Batch operations Update 100 items at once? REST says make 100 PUT requests.
Complex queries Filter by 10 fields, aggregate, join across resources? Query strings get unwieldy.
Over/under-fetching GET returns all 50 fields when you need 3, or you need 4 requests to render one page.
Non-CRUD actions "Send email", "run report" — these are actions, not resources.

Alternatives Comparison

Aspect REST GraphQL gRPC
Endpoints Many (/users, /posts) One (/graphql) Per service/method
Data format JSON (text) JSON (text) Protocol Buffers (binary)
Best for CRUD, public APIs, simple apps Complex UIs, mobile apps Service-to-service, streaming
HTTP caching Works great (GET is cacheable) Custom caching needed Not typically used
Learning curve Low Medium Higher
Browser support Native (fetch) Native (it's just POST) Needs proxy (gRPC-Web)