Skip to content
Backend

GraphQL: An Honest Take After the Hype Cycle

The specific problem it solves brilliantly, the five bills itemized — resolver N+1s, forfeited HTTP caching, query-surface DoS, field-level auth, the toolchain — and the BFF alternative most teams actually need.

4 min read Updated Sep 4, 2026
GraphQL: An Honest Take After the Hype Cycle

GraphQL completed the full hype cycle in record time: 2016's "REST is dead", 2020's default choice for anything with a frontend, and today's quiet wave of "why we moved back" posts. I've built with it, maintained it, and removed it — which purchases the right to an honest assessment. Spoiler: GraphQL solves a real, specific problem brilliantly, and most teams that adopted it didn't have that problem.

One endpoint serving client-shaped queries — with resolver N+1s and caching pain in the shadows

The problem it actually solves

GraphQL exists because of a mismatch: many different clients, each needing different slices of the same data graph, evolving on different schedules. The mobile app wants a lean order list; the web dashboard wants orders with customers, items and shipment timelines; the watch app wants three fields. With REST, that's either chatty (five round-trips composing a screen — brutal on mobile latency), or bloated (one mega-endpoint over-fetching for everyone), or a boutique endpoint per screen (hello, endpoint sprawl). GraphQL's answer: publish a typed schema of the whole graph, let each client declare exactly the shape it needs, get exactly that back in one round-trip. For that problem — genuinely plural clients over a rich data graph, especially with separate frontend/backend teams — it remains the best tool ever built. The typed schema doubling as living, introspectable documentation is a real second prize.

The bills, itemized

  • N+1, promoted to architecture. Resolvers execute per-field, per-node: a query for 50 orders with customers naively fires 51 database hits — the classic ORM disease, except now the query shape comes from the client at runtime, so you can't hand-tune it away. DataLoader-style batching is the fix, and it's not optional garnish; it's load-bearing infrastructure you must build and maintain on every relation.
  • HTTP caching, forfeited. Everything's a POST to one endpoint; URL-keyed caching — CDNs, browser caches, proxies, the entire free layer of the web — no longer applies. Replacements exist (persisted queries, normalized client caches like Apollo's) and each is another system with its own failure modes and cache-invalidation folklore.
  • Unbounded query surface = DoS by design. Clients compose queries; adversaries compose deeply nested ones (orders → items → product → relatedProducts → …). Depth limits, complexity scoring and per-field cost budgets are mandatory security infrastructure that REST simply doesn't need — its endpoints are the allowlist.
  • Authorization moves to the field level. "Who can see customer.email?" must be answered per-field across every path that reaches it — a genuinely harder model than per-endpoint policies, and the source of GraphQL's most embarrassing leaks in the wild.
  • The ecosystem tax. Schema registries, codegen, persisted-query plumbing, resolver tracing (standard APM understands URLs; GraphQL needs its own instrumentation)… the "one endpoint" simplicity is purchased with a toolchain.

The adoption test, stated bluntly

Score one point each:
□ 3+ genuinely different client types (not "web and maybe mobile someday")
□ Rich, deeply connected data graph clients traverse in varied ways
□ Separate frontend team(s) blocked on backend endpoint work today
□ Mobile latency where composing screens server-side measurably matters
□ Team capacity to own DataLoaders, complexity limits, field auth, tooling

4–5: GraphQL will genuinely pay rent. Adopt with eyes open.
2–3: BFF endpoints (screen-shaped REST, owned by the frontend team)
     capture most of the benefit at a fraction of the machinery.
0–1: you wanted typed contracts and nice docs — that's OpenAPI + codegen,
     one afternoon, no resolver farm.

That middle row deserves emphasis because it's where most teams actually live: the backend-for-frontend pattern — a thin layer of screen-shaped REST endpoints, one per view, owned by whoever owns the screen — solves over-fetching and round-trips with boring technology, keeps HTTP caching, keeps per-endpoint auth, and requires zero new failure-mode education. It's less elegant than a universal graph. It also ships this sprint.

Where I've landed

GraphQL is neither the future nor a mistake — it's a specialist. Public-facing product APIs with a true multi-client graph problem (GitHub, Shopify storefronts) validate it daily. Internal admin panels, two-client CRUD apps, and service-to-service traffic (where gRPC's contracts fit better anyway) mostly rented complexity they didn't need. Run the test, count your clients honestly, and remember the quiet rule that outlives every hype cycle: the boring architecture that ships beats the elegant one that's still configuring its schema registry.

Inheriting a GraphQL layer nobody remembers adopting — or sincerely on the fence for a new build? Bring the client list; the answer usually falls out of it in an hour.

Keep reading

Related articles

Backend 5 min read

The Caching Stack: From Browser to Buffer Pool

Five layers walked top to bottom — browser headers, CDN edge with its famous incident, Redis with a job description, database-adjacent options — plus the staleness grid that makes TTLs a product decision.