The redemption of GraphQL

2025-12-10

I have been pushing the use of GraphQL in the projects that I was involved in a lot over the years, which is not exactly the "hip" thing to do these days. In the public perception (or at least the Twitterverse perception) it has fallen from grace, becoming the clunky and over-engineered SOAP of the '2010s era. So why am I still convinced it is worth it? Let me take you on a journey.

Origin story#

Born out of the open source ecosystem at back-then-still-called Facebook, and actually used for the mobile application, it quickly made waves in the web development community. Even though it was declared as an "Application Query Language", it was immediately compared to REST, which is the first big misunderstanding.

GraphQL was designed to solve the very specific problems of rich client side user interfaces that involve a lot of server roundtrips by mainly allowing the client application to craft queries that fetch exactly the information required in as few HTTP requests as possible. Or in simplified terms: It allowed the frontend developer to create a custom REST endpoint on the fly.
Web developers, just discovering React and fat clients were burnt by the tedious back-and-forth between frontend- and backend teams and eager to accept this as the silver bullet.

This shift of power brought a lot of new problems. Here is what Claude has to say about it:

Here are the most common arguments against GraphQL:

Performance & Caching Issues#

  • Complex caching - HTTP caching becomes difficult since most requests use POST to a single endpoint
  • N+1 query problem - Poorly designed resolvers can trigger excessive database queries
  • Over-fetching at resolver level - While clients avoid over-fetching, resolvers might still fetch unnecessary data
  • Query complexity - Deep, nested queries can be computationally expensive

Security Concerns#

  • DoS vulnerabilities - Malicious actors can craft deeply nested queries to overwhelm servers
  • Query complexity attacks - Without proper query analysis and limits, expensive operations can be triggered
  • Introspection exposure - Schema introspection can reveal sensitive API structure in production

Development Complexity#

  • Steep learning curve - Requires understanding of schema design, resolvers, and GraphQL-specific concepts
  • Schema design challenges - Poor schema design can lead to maintenance nightmares
  • Type system overhead - Requires maintaining type definitions and keeping them in sync

Tooling & Infrastructure#

  • Limited HTTP features - Can't leverage standard HTTP status codes, caching headers, and middleware as effectively
  • Monitoring difficulties - Traditional API monitoring tools may not work well with GraphQL
  • File uploads complexity - Handling file uploads requires additional specifications or workarounds

Operational Concerns#

  • Query unpredictability - Harder to predict and optimize for unknown query patterns
  • Debugging complexity - Error handling and debugging can be more complex than REST
  • Rate limiting difficulties - Traditional rate limiting approaches don't work well

These criticisms don't mean GraphQL is inherently bad, but rather highlight scenarios where REST or other approaches might be more suitable.

This is all true, and happens when GraphQL is advertised and used as a replacement for REST (or other, public facing API flavours for that matter). I would even go as far as disqualifying every "GraphQL vs. REST" argument, because even thinking about swapping one for the other is a sign of bad judgement. And I have a story to tell about that.

My contribution to the mess#

Back then, in the middle of the '2010s, I was part of a team that worked on bringing GraphQL into Drupal [1]. Initially it was even sponsored by the Drupal Association with the articulated goal of providing an automatic GraphQL schema for the whole of Drupal core. It was supposed to be great. The whole modern frontend development community would flock around Drupal and use it as a data management system. No pesky backend developers needed any more. Just point and click your way through the Drupal UI, do some "site building"[2] and get your data through the API. Done!

And we delivered this experience in form of version 3 of the GraphQL module for Drupal. It was beautiful. It was gigantic. It sparked my love for unit testing (a story for another day). It was a disaster that ticked all the boxes Claude diligently outlined above.

Making it reflect each little detail within Drupal's humongous data abstraction made the resulting schema incredibly complex, hard to safeguard and maintain and - that's maybe the worst part - it forced knowledge about all the Drupalisms[3] right into the supposedly "decoupled" frontend code, creating a monolith with HTTP requests in between.

We discovered that rather quickly and pivoted to developing version 4 of the GraphQL module for Drupal. Instead of auto-generating the schema, it advocated for manual, per project schema design. To illustrate the difference, let's look at a simple example.

This is a mockup of the auto-generated schema for the default "Page" content type:

type NodePage {
  id: ID!
  label: String!
  field_body: [FieldBody]!
}

type FieldBody {
  raw: String!
  formatted: String!
  format: String!
}

It exposes the fact that each Drupal field can have multiple values on a storage level, and implementation details about how Drupal handles rich formatted user inputs. The vision for version 4 instead looks much more concise:

type Page {
  title: String!
  content: Markup!
}

In the first version, any change on underlying data structure (e.g. switching the editing experience from "Paragraphs" to something sensible) would require a lot of changes in the frontend code. The second version is distilled down to the fundamental requirements, and very unlikely to change.

The idea is to first create the schema as a shared contract between frontend and backend that is void of implementation details and technology decisions. By making it focus on the actual business domain, this could finally live up to the lofty goal of "decoupling".

Also, it was a hard break, barely resembling the initial module, with zero backwards compatibility. Probably this was ill-advised and should have been a separate project. But we felt the responsibility to educate everybody. And we did not want to long-term maintain a solution we did not even use ourselves. That move also made the project incompatible to the Drupal Associations vision for an automatic out-of-the-box API for all of Drupal, and JSON

made it into Drupal core instead. But the lessons were learned.

What I actually learned#

This story is not about roasting Drupal. For better or worse, I still think it is the best open-source content management system, if played to its strengths. But I had to plot the way that led me to the fundamental realization: I do not like GraphQL because of the technical properties it was advertised for. The real deal is that it allows to build a shared understanding of the problem domain that is structured enough to - given the right tooling - directly translates into type definitions and static analysis that feed into the development process.

It somewhat reminds me of my first years at university, where Rational rose was all the rage. We were told to design the whole application as UML diagrams that are transformed into Java interfaces which in turn would be implemented by cheap offshore workforce.

This very much waterfall approach (which I was taught at university as well) was eventually replaced in most cases by more agile practices that actually were able to deliver successful projects. And those have very clear opinions on huge design steps that produce UML diagrams. But with the schema definition being part of the source code lifecycle and the ability to incrementally adapt and improve, it can become a great tool for domain driven design.

It means that we let the business inform the technology, not the other way around. And that's exactly what GraphQL allows me to do: Define the problem domain with all data structures and operations on an abstraction level that allows me to communicate with non-tech stakeholders. Heck, I can even use Voyager to turn it into an interactive UML diagram without using my mouse. But in contrast to other charting tools, it is version controlled together with the rest of the source code. From there, I use GraphQL Codegen to generate different artifacts (e.g. Typescript type definitions, persisted query maps, PHP validators or interfaces ... ) that feed right into the development process. So the GraphQL schema definition becomes living documentation and type definition within the same document.

Enter the robots#

Fast forward to 2025. LLM powered agents have taken the world by storm. And do you know what they really like? Air-tight feedback loops! Like the ones that I had built up around GraphQL type definitions. This super-strict development workflow that already worked great in human teams, but the AI revolution put a jetpack on its back.

First, I discuss the requirements with Claude Code, iterating on the schema definition until it is just right. Then I kick off a little team of Claude Code subagents, specialized in the different layers of the projects technology stack. They all work within clear boundaries and have their feedback loops based on the schema definition and happily churn through the implementation.

This does create a lot of code for me to review, and how I feel about that is a topic for a different post. But in general the result is surprisingly good, requires only a few tweaks and thanks to the strict context and definition it most of the time just clicks together like Lego bricks.

I could do that simply with type definitions in e.g. Typescript, but the GraphQL schema has a bunch of additional benefits. First of all it is technology agnostic, and I can use it PHP, Python and Typescript simultaneously. And what makes it so great for humans also makes it easy to process for agents. It is one file that contains a high-level overview of all available data structures and operations, ideally with clear docblock comments. No implementation details, no need to parse and grep through the whole codebase.

Down the rabbit hole#

Somewhen in 2024, right before MCP entered the stage, I was experimenting with letting agents write GraphQL queries as a form of tool calling. After the landslide success of MCP I put these experiments on ice, but the recent findings by Cloudflare and Anthropic made me think about this again. In a nutshell, they explain shortcomings of the MCP standard, and how modern model's capabilities to write code can be used to mitigate those. An agent can very well write some code to orchestrate multiple tools, instead of calling each tool individually and passing the outputs through its own, non-deterministic context. This can make it more reliable, faster and less expensive.

I think GraphQL can be used to implement the same principle too. Individual GraphQL operations can be statically verified against their schema definition. That means an LLM can quickly iterate on a generated operation without actually having to try the execution. If we somehow also provided a way to chain operations and statically verify that chain (e.g. use one query to create a new object and then pass it into another one), this could be extended to generating full workflows on the fly.

The technical benefits of GraphQL, primarily the ability for the consumer to control the shape of the response, should help agents to fight the current issues with bloated tool responses that fill up the context window and consume too many tokens. An area I would like to look into deeper. Stay tuned, do not adjust your set!