rgoussu@goussu: ~/library/system-design
~/library/system-design cat api-design.md

API design

# Designing service contracts — REST done properly, gRPC, GraphQL, versioning, and evolving APIs without breaking consumers.

Conceptsaved 2026-08-08updated 2026-08-09 #api#rest#grpc#graphql#system-design#contracts

Overview

An API is a promise: once a consumer depends on it, every change is a negotiation. API design is the craft of making that promise clear (resources, verbs, errors), choosing the right style for the job (REST, gRPC, GraphQL, events), and evolving it without breakage. It is the seam where application architecture meets system design — the contract your hexagon's adapters expose and your consumers build against.

Key points

  • REST as resource modeling: nouns not verbs, HTTP methods with their real semantics (idempotency of PUT/DELETE, safety of GET), status codes used honestly, pagination (cursor over offset), filtering, consistent error shape (RFC 9457 problem details).
  • The style trade: REST (ubiquitous, cacheable, loose), gRPC (typed contracts, streaming, fast — internal service-to-service sweet spot), GraphQL (client-shaped queries — pays off with many heterogeneous clients, costs server complexity and caching), webhooks/events for push.
  • Contract-first: OpenAPI / protobuf schema as the source of truth — generated clients/servers, contract tests in CI, and consumer-driven contracts to keep providers honest. The doc formats and the generate-doc-from-code vs generate-code-from-doc trade get their own note: API documentation.
  • Versioning is a last resort: additive, backward-compatible evolution first (tolerant reader: ignore unknown fields); when breaking is unavoidable — explicit versions, deprecation windows, sunset headers. Never silently change semantics.
  • Operational surface: authn/authz (OAuth2/OIDC scopes), rate limiting and quotas, idempotency keys for unsafe retries, timeouts documented as part of the contract.
  • Design for the consumer: the best APIs are designed from the caller's use cases inward, not the database schema outward.
  • To explore: API gateways vs. BFFs, hypermedia (when HATEOAS earns its keep), gRPC error model.

Practice

  • Build your own URL shortener (source) — a deliberately small surface: resource modeling, honest status codes, and idempotent creation, done right end to end.
  • Contract-first kata (source) — write the OpenAPI document before the code for a small service, generate client and server stubs, and put a contract test in CI.
  • REST endpoint, re-served as gRPC (source) — port one of your endpoints to a protobuf contract with server streaming; the style trade stops being theoretical.
  • Build your own web server (source) — Crickett's challenge: HTTP from the socket up, where method semantics, status codes, and headers earn their meaning.
  • Build a load balancer & rate limiter (source) — the operational surface (429, Retry-After, rate-limit headers) as running code instead of a paragraph in the docs.
  • RealWorld full-stack build (source) — implementing a fixed public REST contract exactly, pagination and error semantics included, with someone else's tests as the judge.

Related