API Design — REST, RPC, GraphQL
Resource-oriented REST is the default. RPC (gRPC) for internal high-throughput. GraphQL for flexible client-driven queries. Pick by fit, not fashion.
**REST maturity levels (Richardson)**: Level 0 = RPC over HTTP (single endpoint, verbs in body). Level 1 = resources + POST only. Level 2 = resources + HTTP verbs (the 'REST' most teams ship). Level 3 = HATEOAS (hypermedia — links in responses drive navigation; rarely achieved in practice).
REST best practices: resource-oriented URLs (nouns: /users/42/orders, not /getUserOrders); correct verbs (PUT = replace, PATCH = partial, POST = create-or-misc); status codes carry semantics (201 Created with Location header on POST; 409 Conflict on constraint violation; 429 Too Many Requests for rate limiting); consistent error body; content negotiation via Accept/Content-Type; pagination via cursor (for stable ordering under writes) or offset (simpler but drift).
Idempotency formally: an operation is idempotent if f(f(x)) = f(x). GET, PUT, DELETE are idempotent by spec. POST typically isn't — use Idempotency-Key: <uuid> header; server stores (key → response) for a window; retries with same key return the cached response. Essential for payment APIs.
**gRPC / Protobuf**: schema-first IDL (.proto), code generation for 10+ languages, HTTP/2 multiplexing + streaming (client-, server-, or bidi). Binary serialization = 3-10× smaller than JSON, faster parse. Tooling: grpcurl, Buf, grpc-gateway (REST-to-gRPC proxy for browser clients).
**GraphQL**: single endpoint, query describes shape (`query { user(id: 42) { name orders { total } } }`). Features: queries, mutations, subscriptions (via WebSocket). Gotchas: **N+1 resolver problem** (resolve orders then for each order resolve its items = N+1 queries; fix with DataLoader batch+cache); **security** (arbitrary nesting depth → DoS; use query depth limits, cost analysis); **caching** harder than REST (POST to single endpoint = unfriendly; use persisted queries + GET).
Versioning strategies: URL path (/v1/, /v2/) — simplest, breaks clients on hard cutovers; media type / Accept header (Accept: application/vnd.api+json; version=2) — clean, discoverable via content negotiation; query parameter (?version=2) — easy but easy to forget; evolve backwards-compatible (additive only, deprecate with sunset headers) — ideal when possible.
Rate limiting: token-bucket / leaky-bucket per client. Return 429 Too Many Requests with Retry-After and X-RateLimit-Remaining / X-RateLimit-Reset headers. Tier by API key, by IP for anonymous, by user for authenticated.
Auth: OAuth 2.0 (authorization framework) + OIDC (identity on top of OAuth). For service-to-service: mTLS, signed JWT, API keys. For browser: OAuth authz code + PKCE, short-lived JWT access + refresh tokens. Never invent auth — too many edge cases.
Documentation & contract testing: OpenAPI (REST) / Protobuf (gRPC) / GraphQL schema is the contract. Generate docs (Swagger UI, Redoc), mock servers (Prism), clients (openapi-generator). Contract tests (Pact) verify producer+consumer stay compatible. Essential for any multi-team API.
Grounded on https://restfulapi.net/
Next up
Consistency Models — CAP, PACELC, strong vs eventual
Distributed systems must pick their consistency guarantees. CAP is the headline; the real design lives in eventual, causal, and read-your-writes nuances.