The Over-Fetching Problem Is Real — But So Is GraphQL's Complexity Tax
REST's biggest pain point is over-fetching: a /users endpoint returns 30 fields when your mobile app only needs 3. On slow networks, this wastes bandwidth and slows down the UI. GraphQL elegantly solves this — you request exactly {name, avatar, lastSeen} and that is all you get.
But GraphQL introduces its own complexity tax. You need a schema definition language, resolvers for every field, a query execution engine, and client-side caching (Apollo, urql, or Relay). Error handling is non-standard — HTTP status codes are always 200, with errors nested in the response body. Monitoring and observability tools that work great with REST endpoints need specialized GraphQL plugins.
For a team of 2-3 developers building an MVP, this complexity tax often outweighs the benefits. For a team of 20+ working on a complex product with multiple clients (web, mobile, third-party integrations), GraphQL's upfront investment pays massive dividends.
Performance: The N+1 Problem Nobody Warns You About
GraphQL's flexibility is also its performance trap. When a client queries {users { posts { comments } }}, the naive implementation executes one query for users, then N queries for posts, then N×M queries for comments. This N+1 problem can turn a single GraphQL query into hundreds of database calls.
The solution — DataLoader pattern for batching and caching — works but requires discipline. Every resolver that touches a database needs to use DataLoader, and developers need to understand why. REST APIs naturally avoid this because each endpoint is designed to return a specific, optimized dataset.
In our experience, teams that adopt GraphQL without addressing N+1 issues early end up with APIs that are slower than the REST equivalents they replaced. The fix is not hard, but it requires awareness that many GraphQL tutorials skip.
When REST Wins: Public APIs, Caching, and Simplicity
REST has built-in advantages that GraphQL cannot easily replicate. HTTP caching works out of the box — CDNs, browser caches, and reverse proxies all understand GET /users/123 and can cache the response with standard Cache-Control headers. GraphQL queries are typically POST requests with unique bodies, making HTTP-level caching nearly impossible without additional infrastructure like persisted queries.
For public APIs, REST is still the clear winner. Developers understand REST intuitively — they can explore your API with curl or a browser. GraphQL requires a client, schema knowledge, and query construction. Stripe, Twilio, and most successful API-first companies use REST for good reason.
REST is also easier to version, rate-limit, and document. OpenAPI/Swagger generates beautiful interactive docs automatically. GraphQL introspection provides similar discoverability but assumes more technical sophistication from API consumers.
The Hybrid Approach: Using Both in 2026
The most pragmatic teams in 2026 use both. A common pattern: REST for simple CRUD operations and public APIs, GraphQL for complex internal data aggregation where multiple frontend clients need different data shapes from the same backend.
Another emerging pattern is tRPC for TypeScript-heavy teams — it provides end-to-end type safety like GraphQL but with REST-like simplicity and zero schema overhead. For full-stack TypeScript applications (Next.js + Node.js), tRPC has become a serious alternative to both REST and GraphQL.
The key insight: GraphQL is not a replacement for REST. It is an additional tool that solves specific problems — primarily the coordination challenge between frontend teams that need different data shapes and backend teams that want a single, maintainable API layer.