Web Development

REST API Design

In 2000, Roy Fielding defended his dissertation describing the REST architectural style. He was working on the HTTP specification and realized: if an API follows the same principles as the web (resources, identifiers, methods, representations), clients can work with it without knowing implementation details. Today REST is the dominant API style. The irony is that most 'RESTful APIs' violate at least one of Fielding's key constraints.

  • **Stripe API:** the gold standard of REST design - consistent resource naming, predictable HTTP status codes, detailed error messages, cursor-based pagination, and idempotency keys for POST requests
  • **GitHub API:** uses cursor pagination via Link headers (rel=next, rel=last), rate limiting via X-RateLimit-* headers, and partial responses via ?fields=id,name
  • **OpenAPI/Swagger:** the standard for documenting REST APIs; enables automatic client library generation from a spec file; over 40,000 public APIs use OpenAPI

Resource-Oriented Design

Half of bad REST APIs are bad for one reason: developers think in verbs, not nouns. /getUser, /createOrder, /deleteProduct - that is RPC, not REST. REST is built around resources: /users, /orders, /products. The action on a resource is determined by the HTTP method. This might seem like formality, but it brings real benefits: predictable APIs, a standard cacheable GET, and idempotent PUT/DELETE.

HTTP method semantics: GET (read, cacheable, idempotent), POST (create, not idempotent), PUT (replace entirely, idempotent), PATCH (partial update, conditionally idempotent), DELETE (remove, idempotent). HTTP status codes to know: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity, 429 Too Many Requests, 500 Server Error.

Which HTTP method should correctly be used to cancel an order (changing status from 'confirmed' to 'cancelled')?

API Versioning

API versioning is not an optional luxury. Once an API is public or consumed by a mobile application, breaking backward compatibility is not an option. Users on old app versions must keep working. Several strategies exist: version in the URL (/api/v1/), in a header (Accept: application/vnd.api+json;version=1), or in a subdomain (v1.api.example.com). Version in the URL is the most popular - it shows up in logs and is easy to test.

API version semantics: major version (v1, v2) - breaking changes (removing fields, changing types); minor - additive changes (new endpoints, optional fields). Rule: never remove fields in a minor version. Deprecation cycle: announce deprecation, give at least 6-12 months, monitor usage via metrics, then remove. Sunset header: Sunset: Sat, 01 Jan 2026 00:00:00 GMT.

API v1 returns an 'address' field as a string. In v2 it should be an object { street, city, country }. What is the correct migration strategy?

Pagination and Filtering

An API without pagination is a ticking time bomb. A list of 50 records works great. A list of 50,000 kills the database and times out the client. Three main pagination approaches have different tradeoffs: offset-based is simple to implement but breaks under inserts; cursor-based is stable under data changes; keyset-based is optimal for performance. The right choice depends on the data access pattern.

Offset pagination: LIMIT N OFFSET M - simple, supports random access. Problem: when new records are inserted before the current offset, pages shift. Cursor pagination: cursor = opaque token (base64 of last ID or timestamp). Keyset pagination: WHERE id > last_id ORDER BY id LIMIT N - uses the index, reads no extra rows. Standard response shape: items, total (for offset), nextCursor (for cursor), hasMore.

An API returns pages using offset pagination. While a user reads page 2, new records are inserted at the beginning of the list. What will the user see on page 3?

HATEOAS and REST Maturity

HATEOAS (Hypermedia As The Engine Of Application State) is the final level of the Richardson Maturity Model for REST. The API response contains both data and links to available actions. Instead of the client knowing the URL /orders/{id}/cancel, the server says: 'here is the order, here is what can be done with it right now'. This makes the API self-documenting and lets the server change URLs without updating clients.

Richardson Maturity Model: Level 0 (POX - one endpoint), Level 1 (Resources - separate URLs), Level 2 (HTTP Verbs - correct methods), Level 3 (HATEOAS - hypermedia controls). Most 'REST APIs' operate at Level 2. HAL (Hypertext Application Language) - popular HATEOAS format with a _links object. JSON:API spec - stricter standard that includes HATEOAS.

A REST API with correct HTTP methods and resources is automatically RESTful

Most so-called REST APIs operate at Richardson Maturity Level 2; true REST (Level 3 with HATEOAS) is rarely implemented

Roy Fielding (who coined REST) has repeatedly stated that what the industry calls REST is not REST. HATEOAS is a mandatory part of REST. That said, Level 2 APIs (resources + HTTP methods) deliver 90% of the benefit with minimal complexity, and this is the accepted practical compromise.

A client uses a HATEOAS API. The server changed the order cancellation URL from /orders/:id/cancel to /orders/:id (PATCH). Does the client need to be updated?

Key Ideas

  • **Resource-oriented design** - nouns in URLs, HTTP method determines the action; Location header on 201 Created
  • **Versioning** - major version in the URL (/api/v2/); never break existing clients without a deprecation cycle
  • **Cursor vs Offset pagination** - cursor is stable under data changes; offset is simple but produces duplicates on mid-list inserts
  • **HATEOAS** - the client reads URLs from the response, does not hardcode them; lets the server evolve structure without updating clients

Related Topics

REST API Design defines the contract between client and server:

  • Node.js and Express — Express provides the tools for implementing REST APIs - routing, middleware, error handling
  • GraphQL — GraphQL is an alternative to REST for complex data-fetching requirements; often used alongside REST APIs

Вопросы для размышления

  • Stripe uses idempotency keys for POST requests (client generates a unique key for each request). How does this solve the duplicate request problem on network timeout?
  • GraphQL vs REST: when does GraphQL win on developer experience and when is REST the better choice?
  • How do you design a REST API for bulk operations (create 1,000 users in one request)? What are the tradeoffs of different approaches?

Связанные уроки

  • net-21-http-basics
REST API Design

0

1

Sign In