#2 API Design
Last reviewed 2026-08-10
What it is
The contract you ship outlives your code
An API is the contract between client and server — how the outside world asks your service to do work. Once v1 ships it's far harder to change than the implementation behind it; your API decisions outlive most of your code.
Three protocols dominate: REST, GraphQL, gRPC — each trades developer experience, network efficiency, and tooling. Picking matters, but a good API is mostly predictability, idempotency, and clear errors, not the protocol.
Core concepts
Things every API has to handle
- Resource modeling Name entities and identifiers up front. /users/{id}/orders/{id} is boring and unambiguous — exactly what you want.
- Versioning URL prefix (/v1/), header, or schema-version field. Pick one and stick to it — v1 lives forever, plan for it.
- Idempotency keys Any retryable non-GET needs an Idempotency-Key header so retries don't double-charge or double-create.
- Pagination Cursor-based for anything that grows or changes (feeds, search); offset-based only for static, bounded lists.
- Errors Structured: an error code (clients switch on it), a human message (for the log), and a request id.
- Auth Authorization header, never a query param — query strings leak into CDN logs, browser history, and screenshots.
Variants
REST vs GraphQL vs gRPC
REST
request/response
GET /users
GraphQL
single endpoint
{ user(id) }
gRPC
binary RPC
.proto
- REST HTTP verbs over resources. CDN-cacheable, curl-debuggable, universally supported. Boring is good — the right default for almost any external API.
- GraphQL One endpoint, the client picks its fields. Great when one backend feeds many frontends (web/iOS/Android/partners). Cost: complex caching, N+1 risk, custom tooling.
- gRPC Protobuf over HTTP/2 with bidirectional streaming. Fastest on the wire, strict schema, generated clients in every language. Great internal service-to-service; browsers need a gRPC-Web shim.
BFF (backend-for-frontend) is a useful middle ground: a thin REST/GraphQL service per client calling internal gRPC — the client gets exactly what it needs, internal services stay strict.
Trade-offs
What you give up
- REST is easy to onboard but over-fetches (too much data) or under-fetches (many round-trips for related resources).
- GraphQL is flexible but the server must defend against expensive queries (depth limits, complexity scoring, persisted queries).
- gRPC wins on performance but loses the HTTP ecosystem: no easy CDN caching, no browser DevTools tab, no curl one-liners.
- Versioning is painful regardless — teams promise a clean v2 migration and rarely actually deprecate v1.
- Idempotency is the difference between a retry and a duplicate charge; skip it and you'll eventually corrupt customer data.
In an interview
What to listen for
- “Mobile & web, different needs” GraphQL or BFF — don't make mobile do 5 round-trips to render one screen.
- “Internal microservices” gRPC for hot paths, REST for management and admin endpoints.
- “Public API for third parties” REST + an OpenAPI spec — stable URLs, documented error codes, generous rate limits.
- “Webhooks” / “outbound POST” Signed bodies (HMAC), idempotency keys, retries with exponential backoff, a replayable delivery log.
Always say what happens on retry: “POST /payments takes an Idempotency-Key, so a duplicate returns the original response without a second charge.” That sentence beats naming any protocol.