GraphQL vs REST Decision
Architecture Diagram
When GraphQL Wins
GraphQL solves specific problems well. If you are building a mobile application that displays different data on different screens, REST forces you into one of two bad options: over-fetch everything on every endpoint, or create dozens of screen-specific endpoints (the BFF pattern done manually). GraphQL lets the client declare exactly what it needs.
GitHub migrated to GraphQL for their public API in 2017. Their REST API returned massive payloads for repository queries because every endpoint included the full resource representation. With GraphQL, a client fetching a list of repository names transfers 10x less data.
Shopify runs one of the largest GraphQL APIs in production. Their Storefront API serves thousands of merchant storefronts, each needing different combinations of product, collection, and checkout data. A single flexible schema replaced what would have been hundreds of specialized REST endpoints.
When REST Wins
REST wins when simplicity and caching matter more than query flexibility. A CRUD API for a web application with predictable data needs does not benefit from GraphQL. The overhead of schema definition, resolver implementation, and client setup adds complexity without proportional value.
REST also wins for public APIs where you do not control the consumers. Stripe, Twilio, and most payment providers use REST because it is universally understood. The learning curve is lower, tooling is mature, and HTTP semantics (status codes, content negotiation, caching headers) work without additional infrastructure.
The N+1 Problem
Consider a GraphQL query that fetches 20 posts with their authors. Without optimization, the resolver fetches 20 posts, then makes 20 separate database calls to fetch each author. This is the N+1 problem and it will destroy your database under load.
DataLoader solves this by batching and deduplicating requests within a single execution context. Instead of 20 author queries, DataLoader collects all 20 author IDs and makes one SELECT * FROM authors WHERE id IN (...) call. This is not optional. Every production GraphQL server needs DataLoader or an equivalent.
Federation for Microservices
Apollo Federation lets multiple GraphQL services contribute to a single schema. The orders team owns order types, the users team owns user types, and the gateway composes them into a unified graph. This is powerful for organizations with 5+ backend teams that want a consistent API without a monolithic gateway team.
The alternative is schema stitching, which is simpler but puts the composition burden on the gateway. Federation is more work upfront but scales better organizationally.
The Middle Path: REST with JSON:API or OpenAPI
If your main complaint about REST is inconsistent response shapes, consider JSON:API or a strictly enforced OpenAPI spec before jumping to GraphQL. JSON:API supports sparse fieldsets (partial responses) and compound documents (included relationships). This gets you 70% of GraphQL's data-fetching benefits with REST's simplicity.
Key Points
- •GraphQL reduces over-fetching and under-fetching by letting clients request exactly the fields they need. This matters most for mobile apps on slow networks
- •REST has simpler caching semantics. HTTP caching works out of the box with GET requests, ETags, and CDN integration. GraphQL POST requests require custom cache strategies
- •The N+1 query problem in GraphQL is real and requires explicit solutions like DataLoader. Without batching, a single GraphQL query can trigger hundreds of database calls
- •GraphQL federation (Apollo Federation, GraphQL Mesh) lets multiple teams own parts of a unified schema. This is its strongest advantage in microservice architectures
- •Public APIs are almost always better served by REST. Documentation is simpler, caching works natively, and consumers do not need to learn a query language
Common Mistakes
- ✗Adopting GraphQL because it is trendy without a concrete problem like mobile over-fetching or multi-team schema ownership to justify the complexity
- ✗Exposing your database schema directly through GraphQL types. The API schema should represent your domain model, not your table structure
- ✗Allowing unbounded query depth without complexity limits. A malicious or careless client can craft a query that brings down your server