GraphQL Is Overengineered for Most Use Cases

· 5 min read
graphql api simplicity

Do You Have Facebook’s Problems?

GraphQL is genuinely clever technology. I’ve used it, I’ve enjoyed parts of it, and I am not here to tell you it’s bad.

I am here to ask a slightly awkward question: how many clients does your API have, and how different are their data requirements?

If the answer is “one web app, and not very”, you have just talked yourself out of GraphQL and saved everybody a fortnight. Because GraphQL was built by a very large company with a lot of clients, a lot of teams, and a genuinely hard problem about who gets to decide what data an endpoint returns. Adopting the solution without the problem gets you all of the cost and none of the payoff.

What You Sign Up For

The demo always looks marvellous. You write a query in a nice playground, you get exactly the fields you asked for, and somebody in, the room says “why would anyone use REST”.

Then you build it.

You define a schema. You write resolvers, one per field, and then more because the nested ones need their own. You work out field-level authorisation, because “can this user read this object” is now a question with a different answer per field and per path through the graph. You add a client library. You configure code generation so the types stay honest.

And what did you want, at the bottom of all this? To get some data out of a database and onto a page. REST did that with a verb, a URL and some JSON.

Man buried under an enormous pile of paperwork

The N+1 You Invited In

The flexibility that sells GraphQL is also what makes it hurt.

A naive implementation resolves field by field, which means one database query per field per item, and a list of fifty users with an author on each becomes a very sad afternoon for your database. Dataloaders fix it, batching the calls behind the scenes, and they work well.

But notice what just happened. You added a layer of infrastructure to solve a performance problem that your architecture created. The REST version of that endpoint was one query with a join.

Caching Stops Being Free

This is the one that catches teams out latest, and hardest.

With REST, HTTP caching is simply there. Browsers cache. Proxies cache. Your CDN caches. Conditional requests work. Nobody has to be clever about it.

With GraphQL, everything is a POST to a single endpoint, so every one of those layers politely declines to help. You get a normalised client cache instead, which is powerful and also a thing you now have to understand, configure, and debug when it hands somebody stale data with great confidence.

Overfetching Was Never Costing You Much

The headline pitch is that you fetch exactly what you need. Fair enough. Now price it.

For most APIs, shipping a few extra fields you didn’t render costs some bandwidth, and bandwidth is cheap. Building and maintaining a schema, resolver layer, batching layer and client cache costs developer time, and developer time is the most expensive thing your team owns.

If you’re sending megabytes to a phone on a bad connection in a country where data is expensive, that maths flips and the pitch is real. If you’re sending 4KB to a laptop, you’ve spent a fortnight optimising something nobody was going to notice.

The Bigger Attack Surface

One more thing worth knowing before you commit: clients get to compose their own queries.

That is the feature. It is also the risk. Without limits, someone can craft a deeply nested query that asks your database to do something ruinous, and they don’t need to be malicious to do it, just curious. So you add depth limiting. Then query cost analysis. Then complexity budgets and maybe persisted queries, at which point you have a fixed set of allowed operations, which is, if you squint, endpoints.

When It Genuinely Earns Its Keep

None of this means never. There are situations where GraphQL is clearly the right answer:

  • Several clients, mobile and web and third parties, with genuinely different data needs
  • Complex interconnected data with lots of optional fields and lots of ways to traverse it
  • Public APIs where consumers need flexibility you cannot anticipate
  • Large organisations where front-end and back-end teams need to stop blocking each other

Notice these are mostly organisational problems rather than technical ones. That’s the tell.

Adopting the solution without the problem gets you all of the cost and none of the payoff.

The Argument Against

The tooling really is excellent, and I don’t want to underplay it. Type safety end to end, generated clients, introspection, a schema that doubles as documentation and cannot silently drift from the implementation. In several respects the developer experience beats anything I’ve had with REST, where the docs are a wiki page and the types are aspirational.

And once a team knows it, some things do get simpler. No more endpoint sprawl. No more forty-minute discussions about whether it should be plural. No more version three of an endpoint that exists because one screen needed one extra field. Types and resolvers, and get on with it.

Where I Might Be Wrong

It’s entirely possible I’ve used it for the wrong things. Retrofitted onto a REST-shaped backend, with a team learning it as they went, which is roughly the worst conditions you could pick. In the right context with a team that already knows it, the complexity I’m describing may mostly disappear into things they set up once and never think about again.

The ecosystem also keeps getting better. Federation, persisted queries and the newer server frameworks address several of my complaints directly.

Before You Commit

  1. Write down the problems you expect GraphQL to solve. Then check you actually have them
  2. Count your clients and how much their data needs really differ. Be honest
  3. One team, one API, one client? Start with REST. JSON:API if you want structure imposed for you
  4. TypeScript everywhere and a single front end? Look at tRPC before you look at GraphQL
  5. If you do adopt it, budget for dataloaders, depth limits and cost analysis from day one, not later
  6. Whatever you pick, check what happens to caching. It’s the cost people forget

Boring architecture ships. Pick the interesting option only when the boring one has actually failed you.

Thumbs up

Until next time, happy coding!

Available for rescue and re-platforming work

I take over platforms that already exist and are in trouble. Node, TypeScript, React and Laravel, mostly in regulated or high-traffic environments. If something needs rescuing, re-platforming or finishing, my full history is on the CV.

Related Posts

Comments