Get AI summaries of any video or article — Sign up free
I Am Done With Graph QL After 6 Years thumbnail

I Am Done With Graph QL After 6 Years

The PrimeTime·
5 min read

Based on The PrimeTime's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

GraphQL’s main downside is not its concept of queries, but the security and performance burden created by letting untrusted clients send arbitrary query shapes.

Briefing

GraphQL’s biggest problem isn’t that it’s “bad”—it’s that exposing a query language to untrusted clients dramatically expands the security and performance burden, and the mitigations add enough complexity that many teams end up worse off than if they used a conventional, typed API. After years of championing GraphQL, the speaker says the tradeoffs become unacceptable once security, maintainability, and operational reliability matter more than developer convenience.

A central risk is attack surface. Because clients can request arbitrary shapes of data, attackers can craft queries that strain authorization logic, overwhelm CPU, or amplify resource usage. Authorization is the most widely understood threat: a fully self-documenting GraphQL endpoint requires field-by-field authorization in the context of the current user, not just coarse checks at the object level. The speaker argues that this is harder than REST’s typical model, where developers authorize endpoints rather than every field in every nested context.

Denial-of-service risk is framed as even more severe. GraphQL servers can’t assume requests are equally “expensive,” and there’s no simple, universal limit on query size or computational cost. Even with an empty schema, introspection types can be cyclic, enabling valid queries that return large JSON payloads or force the server to spend significant CPU time parsing and resolving. The speaker describes testing an attack against a popular GraphQL API explorer and seeing 500 responses after about ten seconds from a very small query, with the implication that a short curl loop can effectively “DoS yourself.”

Mitigations exist—query complexity limits, rate limiting, maximum depth, and stricter schema design—but they’re delicate to implement correctly. Complexity estimation can be wrong, especially with cyclic schemas (e.g., tags related to related tags), where errors can compound exponentially. Default maximum depth settings (the speaker cites GraphQL Ruby’s default of 13) help but don’t fully address the underlying issue: deeply nested queries are only one dimension of cost.

Performance concerns extend beyond security. The speaker highlights the N+1 problem caused by nested resolvers that trigger repeated database or HTTP calls, and argues that GraphQL often pushes data-fetching logic into the transport layer. Once authorization is integrated into resolvers, it can create a new class of N+1-like performance issues where authorization checks themselves hit the database repeatedly. Debugging also becomes harder because much of the behavior—authorization, data loading, error handling—occurs inside the GraphQL framework, leaving developers to interpret stack traces embedded in GraphQL error responses.

The conclusion is a conditional recommendation: GraphQL may still make sense when teams control all clients, use a statically typed language, and need GraphQL’s type-safe, self-documenting query experience. Otherwise, the speaker prefers OpenAPI 3.0-compliant JSON REST APIs, with tooling that generates typed clients and server handlers from specs. The broader message is that GraphQL’s flexibility can be a “hype-cycle” fit for early stages, but in sufficiently complex environments it can force teams into building and maintaining expertise—and infrastructure—to manage risks that simpler API styles avoid.

Cornell Notes

GraphQL’s flexibility comes with a security and performance tax because clients can send arbitrary queries that are hard to bound. The most serious issues highlighted are authorization complexity (field-by-field checks in user context) and denial-of-service risk (queries with unpredictable cost, including introspection-based attacks). Complexity limits, depth limits, and rate limiting can reduce harm, but they’re tricky to implement correctly—especially with cyclic schemas where estimates can be wildly wrong. The result is more framework-driven logic, harder debugging, and more integration testing. For many teams, the speaker recommends OpenAPI 3.0 REST with typed client/server generation as a simpler alternative once operational constraints dominate.

Why does exposing GraphQL to untrusted clients increase risk compared with typical REST endpoints?

GraphQL lets clients request arbitrary combinations of fields and nesting, so the server must handle unbounded query shapes. That means attackers can craft queries that stress authorization paths, force expensive resolver execution, or trigger large responses. In REST, the usual pattern is to authorize at the endpoint level, which is simpler to reason about than authorizing every field in every nested context.

What makes denial-of-service risk feel uniquely dangerous in GraphQL?

GraphQL requests can’t be assumed to have uniform cost. A small query can still cause heavy CPU work during parsing and resolution, and introspection can enable cyclic type structures that allow valid queries to return megabytes of JSON or consume significant CPU time. The speaker describes a test against a popular API explorer where a short query produced 500 responses after about ten seconds, illustrating how a curl loop could “DoS yourself.”

How do query complexity limits and maximum depth help, and why are they fragile?

Complexity limits estimate the “cost” of resolving fields and reject queries exceeding a threshold; maximum depth blocks deeply nested queries. But complexity estimation can be wrong—especially when schemas contain cycles (like tags related to related tags). If the estimate assumes a bounded number of children but real data violates that assumption, attackers can create queries whose true complexity is far higher than the server’s estimate, leading to under-rate-limiting.

What performance problems does GraphQL introduce beyond security?

Nested resolvers can trigger the N+1 problem: if a resolver calls a database or external API and it sits inside a list of N items, it may perform N separate calls. The speaker also argues that authorization integration can create another N+1-like pattern where authorization checks themselves repeatedly hit the database, increasing total work even when data fetching is optimized.

Why does GraphQL debugging and testing become more difficult in practice?

Much of the critical behavior—authorization, data loading, error formatting—happens inside the GraphQL framework rather than application code. That shifts failures into GraphQL error responses and stack traces that are harder to map back to business logic. Because field-level and argument-level behaviors depend on query execution, teams often need extensive integration testing by running real GraphQL queries.

What alternative does the speaker recommend, and under what conditions?

For many teams, the speaker recommends OpenAPI 3.0-compliant JSON REST APIs, especially when operational simplicity matters. The key condition for sticking with GraphQL is having control over all clients, using statically typed languages, and leveraging improved tooling for typed client generation. Otherwise, typed REST with spec-driven tooling can deliver similar type-safety benefits without GraphQL’s query-language risk profile.

Review Questions

  1. What specific authorization requirement makes GraphQL harder to secure than endpoint-based REST authorization?
  2. Explain how cyclic schemas can break query complexity estimation and lead to under-mitigated attacks.
  3. List two reasons GraphQL can increase integration testing and debugging effort compared with REST.

Key Points

  1. 1

    GraphQL’s main downside is not its concept of queries, but the security and performance burden created by letting untrusted clients send arbitrary query shapes.

  2. 2

    Field-by-field authorization in user context is required for safe GraphQL, and it’s harder than authorizing endpoints in a REST-style API.

  3. 3

    Denial-of-service risk in GraphQL stems from unpredictable query cost, including parsing and introspection-driven attacks.

  4. 4

    Query complexity limits and maximum depth can mitigate attacks, but they’re fragile—especially with cyclic schemas where estimates can compound exponentially.

  5. 5

    GraphQL can worsen N+1 performance issues because nested resolvers and authorization checks may repeatedly hit databases or external services.

  6. 6

    Framework-driven execution (data loaders, authorization, error handling) shifts failures into GraphQL error responses, making debugging and integration testing more painful.

  7. 7

    A common alternative is OpenAPI 3.0 REST with typed client/server generation, particularly when teams don’t control all clients or don’t want GraphQL’s operational complexity.

Highlights

A short, valid GraphQL query can still consume significant CPU time, and introspection cycles can enable megabyte-scale JSON responses—even without a “real” schema.
Authorization in GraphQL isn’t solved by object-level checks; safe operation requires field-level authorization tied to the current user and the context of each fetch.
Complexity-based throttling is delicate: cyclic schemas (like related tags) can make real query cost far higher than estimated, letting attackers slip through.
GraphQL’s flexibility often forces business logic into the transport layer, increasing integration testing and making stack traces harder to interpret.
For many teams, typed REST generated from OpenAPI 3.0 can preserve type-safety benefits without the query-language risk profile.

Topics

  • GraphQL Security
  • Authorization
  • Denial of Service
  • Query Complexity
  • N+1 Problem
  • OpenAPI REST

Mentioned

  • API
  • CPU
  • DoS
  • HTTP
  • HTTP2
  • JSON
  • N+1
  • OAS
  • REST
  • SOAP
  • SQL
  • SPA
  • TLS
  • API Explorer