You Don't Need GraphQL
GraphQL is often treated as a “silver bullet” by developers who have only seen its marketing pitch (flexible queries! fewer endpoints! single source of truth!), but in reality, it introduces a huge amount of complexity, overhead, and hidden tradeoffs that make it a poor fit for the majority of applications.
Let’s go through the comprehensive critique, structured by categories:
🧩 1. Conceptual and Architectural Overhead
❌ Too Complex for Most Use Cases
Most apps — especially CRUD or REST-like systems — don’t need the flexibility GraphQL offers. When you have 10–20 endpoints and predictable data flows, REST is simpler, more readable, and more cache-friendly.
GraphQL requires: • Defining schemas (SDL), • Implementing resolvers for every field, • Managing N+1 queries, • Type systems and build tooling, • Query validation, introspection, and complexity control.
That’s a lot of ceremony for something REST can do with GET /users/:id and GET /users/:id/posts.
❌ Over-Engineering Risk
Developers often reach for GraphQL because it feels “modern.” But for 80% of apps, it’s solving problems that don’t exist — or creating new ones: • A typical startup MVP doesn’t need partial field fetching or dynamic queries. • You end up spending time maintaining schema infrastructure instead of business logic.
⚙️ 2. Performance and Complexity Issues
❌ N+1 Query Problem
Because each field in a GraphQL query can have its own resolver, a naïve implementation can generate hundreds of database queries:
{
users {
posts {
comments {
author {
name
}
}
}
}
}
Unless you add complex batching, caching, and dataloader mechanisms, this can destroy performance.
❌ Caching Is Hard
HTTP caching (e.g. ETag, Cache-Control) works naturally with REST. GraphQL breaks it — every query is a POST with a dynamic body. You must implement application-level caching, which is hard and fragile.
❌ Overfetching vs Underfetching Isn’t Really Solved
In practice, most clients don’t dynamically compose queries; they just hardcode GraphQL queries — so you end up with the same problem as REST, plus schema complexity.
❌ Query Complexity & Abuse
Without strict limits, clients can send extremely deep or expensive queries that: • Overload the backend, • Expose sensitive data by mistake, • Require rate limiting or query cost analysis systems.
You must implement query depth limiters, complexity analyzers, or whitelisting, adding more infra.
🧱 3. Tooling and Maintenance Burden
❌ Boilerplate Explosion
To implement GraphQL properly, you need: • Schema definitions (SDL or code-first), • Resolvers, • Data loaders, • Authorization middleware, • Schema stitching (if modularized), • Build-time schema generation for clients (Apollo, URQL, Relay).
That’s 10x more moving parts than a clean REST API.
❌ TypeScript Integration Pain
Type safety sounds great, but maintaining schema → types → resolvers → generated client queries in sync is tedious. If your backend or database schema changes, you must rebuild the type chain.
With REST + OpenAPI, it’s often much simpler.
❌ Poor Error Handling
GraphQL returns “partial successes”:
{
"data": {"user": null},
"errors": [{"message": "User not found"}]
}
This is conceptually weird — clients must handle “data” and “errors” separately. In REST, HTTP status codes already express intent cleanly.
❌ Cumbersome Debugging
Tracing errors across nested resolvers is painful — you lose clear endpoint boundaries and stack traces become multi-layered.
🔒 4. Security and Governance Concerns
❌ Query Introspection = Attack Surface
GraphQL introspection exposes your entire API schema. Attackers can use it to: • Discover hidden fields or internal data, • Build targeted denial-of-service queries.
You have to disable introspection in production or secure it manually.
❌ Access Control Complexity
Authorization logic becomes scattered: • Each field may need access control, • Relationships require recursive permission checks.
With REST, you just guard endpoints — much simpler.
❌ Input Sanitization & Query Cost
Because GraphQL accepts complex nested queries with arbitrary arguments, validation becomes nontrivial. Malformed or malicious queries can crash servers or leak data if not deeply validated.
🧠 5. Developer Experience and Team Scaling
❌ Harder to Learn and Onboard
REST is universal and intuitive. GraphQL requires learning: • SDL syntax, • Resolvers and schema-first vs code-first patterns, • Batching, fragments, directives, etc.
Junior or mid developers often misuse it, causing bugs or performance hits.
❌ Tight Coupling Between Frontend & Backend
Ironically, GraphQL often makes teams more dependent: • Frontend needs schema updates for new fields. • Backend needs to version schema carefully to avoid breaking clients. • You can’t evolve the API easily without CI/CD integration of schema generation.
With REST, you can just add v2 endpoints and move on.
📊 6. Operations and Infrastructure
❌ Monitoring and Logging Pain
You lose clear, semantic URLs (like /api/users/123).
Everything goes through /graphql.
This kills simple logging, metrics, and tracing tools — you must parse query bodies just to know what’s being called.
❌ Poor CDN Compatibility
CDNs (Cloudflare, Akamai, etc.) can’t cache POST requests by default. That means you lose cheap global caching, adding backend pressure and latency.
❌ Overhead for Small Teams
Apollo Server, schema registry, caching layer, dataloaders, query limits, etc. — all require ops overhead that small projects cannot justify.
🧩 7. Misalignment with Real-World API Needs
❌ Real APIs Are Not Arbitrary Graphs
Most data models aren’t infinite graphs — they have bounded relationships (e.g. user -> posts -> comments), and exposing deeper connections just invites abuse.
❌ Doesn’t Fit External APIs
For public APIs, GraphQL is hard to document, version, and cache — clients prefer REST or RPC style (e.g. gRPC, tRPC, OpenAPI).
❌ Not Great for File Uploads, Streams, or Webhooks
These need extra conventions (graphql-upload, subscriptions, etc.) — all complex, less standard, and slower.
⚖️ 8. When GraphQL Does Make Sense
There are legitimate use cases: • Aggregating multiple microservices into a single API gateway. • Large teams where many clients (mobile, web, internal tools) need flexible access to shared data. • B2B APIs where consumers need selective field control. • Schema-driven APIs (e.g. with persisted queries, strong governance).
But unless you’re Facebook, Shopify, or GitHub — you likely don’t need it.
🧾 TL;DR Summary
| Category | REST | GraphQL |
|---|---|---|
| Simplicity | ✅ Simple, intuitive | ❌ Complex schema, resolvers |
| Performance | ✅ HTTP cache, CDN friendly | ❌ Requires batching, dataloaders |
| Caching | ✅ Native | ❌ Hard, manual |
| Security | ✅ Predictable | ❌ Deep query + introspection risks |
| Tooling | ✅ Minimal | ❌ Heavy infra (Apollo, schema registry) |
| Versioning | ✅ URL-based | ❌ Must maintain schema evolution |
| Dev Onboarding | ✅ Easy | ❌ Steep learning curve |
| Best Fit | Small/medium apps | Data aggregation or multi-client platforms |
🚨 Final Verdict
GraphQL is a powerful but over-engineered abstraction that’s useful for complex data ecosystems, not for ordinary web apps. In most projects, it adds friction, latency, and risk without delivering tangible benefits over a well-structured REST or RPC API.
