API Style & Pattern Comparisons
Side-by-side trade-offs. Neither column wins — the consumer environment, the data shape and the operational budget decide.
REST vs GraphQL
Server-defined response shapes per endpoint vs a typed graph where each client selects exactly the fields it needs — moving the flexibility, and its cost, from client to server.
Response shapes are stable per consumer, HTTP caching matters, and you want failures visible in status codes.
You would build one endpoint per screen anyway — a BFF gives the same fit with far less machinery.
Simplicity: cache by URL, debug with curl, reason about one endpoint's cost in isolation.
Screens need five calls (under-fetching) or drag 80 fields for 3 (over-fetching), and mobile pays the RTT bill.
Endpoint sprawl management: every new client need is a new endpoint or parameter to design, document and version.
Many client teams with divergent data needs iterate faster than the API team can ship purpose-built endpoints.
A small team runs one first-party client; you inherit resolvers, cost control and authorization complexity for flexibility nobody uses.
Typed schema with introspection, one round trip per screen, no client-specific endpoint sprawl.
An unanticipated nested query fans out into thousands of resolver calls and nobody bounded its cost.
Resolver batching (dataloaders), query-cost limits, per-field authorization, and caching rebuilt above the HTTP layer.
| Dimension | REST | GraphQL |
|---|---|---|
| Who shapes the response | The server, per endpoint | Each client, per query, within the schema |
| Over/under-fetching | Common — fixed shapes fit nobody exactly | Solved for clients, relocated to resolver efficiency |
| Caching | HTTP-native: URL + Cache-Control + ETag | Custom: normalized client caches, persisted queries |
| Failure surface | Status codes intermediaries understand | 200 with an errors array — monitoring must read bodies |
| Cost control | Per endpoint, known at design time | Per query — needs depth limits and cost analysis at runtime |