Back to list

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

CategoryRESTGraphQL
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 FitSmall/medium appsData 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.