Networking & Communication

REST APIs

18 min Lesson 5 of 10

REST APIs

REST (Representational State Transfer) is the dominant architectural style for building HTTP-based APIs. First described by Roy Fielding in his 2000 doctoral dissertation, it is not a protocol or a standard — it is a set of constraints that, when followed, produce systems that are scalable, simple to understand, and easy to evolve. Almost every public API you have consumed — Twitter, Stripe, GitHub, Twilio — is a REST API.

The Six REST Constraints

Fielding defined six constraints. Two are the most important for everyday design:

  1. Uniform Interface — Resources are identified by stable URIs; interactions happen through representations (JSON, XML) and standard HTTP methods.
  2. Statelessness — Every request contains all the information the server needs. The server holds no session state between requests.
  3. Client–Server separation — UI concerns are decoupled from data-storage concerns.
  4. Cacheability — Responses declare whether they may be cached, enabling proxies and clients to cache aggressively.
  5. Layered System — A client cannot tell if it is talking directly to the server or through an intermediary (load balancer, CDN, gateway).
  6. Code on Demand (optional) — Servers may send executable code (JavaScript) to clients.
Statelessness is the most consequential constraint at scale. Because no session is held server-side, any server in the pool can handle any request. This makes horizontal scaling trivially easy: add servers, point the load balancer at them, done.

Resources and URIs

In REST, a resource is any concept that can be named and addressed. A user is a resource. An order is a resource. A photo album is a resource. Resources are nouns, not verbs.

A URI (Uniform Resource Identifier) names the resource. URI design is the first place REST APIs go wrong. Follow these rules:

  • Use nouns, not verbs. /users not /getUsers. /orders/42/cancel is borderline — better as POST /orders/42/cancellations (treating the cancellation itself as a resource).
  • Use plural nouns for collections. /articles, /products, /invoices.
  • Nest to express ownership, but limit to two levels. /users/7/posts is fine. /users/7/posts/3/comments/1/likes is a maintenance nightmare — flatten to /likes/99.
  • Use kebab-case for multi-word segments. /order-items not /orderItems or /order_items.
  • Keep query parameters for filtering, sorting, and pagination. GET /products?category=electronics&sort=price&page=2.

HTTP Verbs

HTTP methods (verbs) express the intent of the operation. Using them correctly enables caches, proxies, and clients to behave predictably without reading documentation.

HTTP verbs mapped to CRUD operations on a REST resource Method URI Example Meaning Properties GET GET /orders/42 Read a resource Safe · Idempotent Cacheable POST POST /orders Create new resource Neither safe nor idempotent PUT PUT /orders/42 Full replacement Idempotent PATCH PATCH /orders/42 Partial update Idempotent (if designed so) DELETE DELETE /orders/42 Remove resource Idempotent Safe = no side effects on the server. Idempotent = calling it N times has the same effect as calling it once.
HTTP methods and their semantic contracts — the foundation of REST API design.

Idempotency and Why It Matters

An operation is idempotent if performing it multiple times produces the same result as performing it once. This matters enormously in distributed systems because networks are unreliable — clients retry failed requests.

  • DELETE /orders/42 called twice: first call deletes the order, second call gets a 404. The order is still gone — idempotent. ✓
  • POST /orders called twice due to a network timeout: you may create two orders — not idempotent. Use an idempotency key header (Idempotency-Key: <uuid>) to let the server deduplicate.
  • PUT /orders/42 with a full body: replaces the resource to the same state each time — idempotent. ✓
Design tip: Stripe's payment API requires clients to send an Idempotency-Key header on POST requests. The server stores the key and, if it sees a duplicate within 24 hours, returns the original response without charging the card again. Adopt this pattern for any non-idempotent endpoint that handles money, emails, or other irreversible side-effects.

HTTP Status Codes

Status codes communicate the outcome of a request. Use them precisely — do not return 200 OK with an error message in the body.

  • 2xx Success200 OK, 201 Created (include a Location header to the new resource), 204 No Content (for DELETE with no body).
  • 3xx Redirection301 Moved Permanently, 304 Not Modified (enables client-side caching via ETag / If-None-Match).
  • 4xx Client Error400 Bad Request, 401 Unauthorized (not authenticated), 403 Forbidden (authenticated but not allowed), 404 Not Found, 409 Conflict, 422 Unprocessable Entity (validation errors), 429 Too Many Requests (rate limiting).
  • 5xx Server Error500 Internal Server Error, 503 Service Unavailable (use for circuit-breaker responses).

Statelessness at Scale — A Real-World Flow

The following diagram shows a stateless REST request flowing through a production-grade system. Notice that the load balancer can send any request to any application server — there is no sticky session because all state is in the database and token.

Stateless REST request flow through a horizontally-scaled system Client Bearer JWT HTTPS Load Balancer any App Server instance 1 App Server instance 2 App Server instance 3 Cache Redis Database PostgreSQL miss No session stored on any App Server — the JWT carries identity; any instance handles any request
Stateless REST flow: the JWT in every request means any app server can handle it, enabling frictionless horizontal scaling.

Designing Clean REST APIs — Practical Guidelines

Beyond the basics, several practices separate APIs that developers love from those they dread:

  • Version from day one. Use a URL prefix (/v1/, /v2/) or an Accept header. Changing a response shape without versioning breaks every client that exists.
  • Return consistent error shapes. Every error response should have the same JSON structure: error.code, error.message, optional error.details. Stripe and GitHub do this perfectly.
  • Paginate collections. Never return unbounded lists. Use cursor-based pagination for high-volume feeds (?after=cursor) and offset/limit for simple tables (?page=3&per_page=25).
  • Use HATEOAS selectively. Hypermedia As The Engine Of Application State means embedding links in responses ("links": {"self": "/orders/42", "cancel": "/orders/42/cancellations"}). Useful for discoverability; skip it if clients are tightly coupled mobile apps you fully control.
  • Expose only what clients need. Avoid returning 40-field objects when the mobile app uses 5. Consider sparse fieldsets (?fields=id,name,price) or GraphQL if over-fetching is a real cost.
Common pitfall — RPC-style URLs: APIs like POST /createOrder, GET /getUser?id=7, or POST /deleteProduct are not REST — they are RPC over HTTP. They forgo caching, break reverse proxies that optimise by verb, and confuse consumers. If you find yourself putting verbs in URLs, stop and model the resource instead.

REST vs. "REST-ish"

In practice, very few public APIs satisfy all six Fielding constraints — particularly HATEOAS. What most developers call a "REST API" is more accurately a resource-oriented HTTP API. That is fine. The constraints that matter most at scale are statelessness (for horizontal scaling), the uniform interface (for cacheability and predictability), and correct use of HTTP verbs and status codes. Start there.

The next two lessons will cover RPC/gRPC (where REST's verbosity becomes a bottleneck in internal microservice calls) and WebSockets (where REST's request-response model breaks down for real-time data). Knowing REST deeply makes those trade-offs legible.