Skip to content(if available)orjump to list(if available)

GraphQL: The enterprise honeymoon is over

GraphQL: The enterprise honeymoon is over

106 comments

·December 14, 2025

hn_throwaway_99

> The main problem GraphQL tries to solve is overfetching.

My issue with this article is that, as someone who is a GraphQL fan, that is far from what I see as its primary benefit, and so the rest of the article feels like a strawman to me.

TBH I see the biggest benefits of GraphQL are that it (a) forces a much tighter contract around endpoint and object definition with its type system, and (b) schema evolution is much easier than in other API tech.

For the first point, the entire ecosystem guarantees that when a server receives an input object, that object will conform to the type, and similarly, a client receiving a return object is guaranteed to conform to the endpoint response type. Coupled with custom scalar types (e.g. "phone number" types, "email address" types), this can eliminate a whole class of bugs and security issues. Yes, other API tech does something similar, but I find the guarantees are far less "guaranteed" and it's much easier to have errors slip through. Like GraphQL always prunes return objects to just the fields requested, which most other API tech doesn't do, and this can be a really nice security benefit.

When it comes to schema evolution, I've found that adding new fields and deprecating old ones, and especially that new clients only ever have to be concerned with the new fields, is a huge benefit. Again, other API tech allows you to do something like this, but it's much less standardized and requires a lot more work and cognitive load on both the server and client devs.

lateforwork

If you generate TypeScript types from OpenAPI specs then you get contracts for both directions. There is no problem here for GraphQL to solve.

null

[deleted]

iterateoften

Graphql solves the problem. There is no problem here for openapi to solve.

See how that works?

c-hendricks

What about the whole "graph" part? Are there any openapi libraries that deal with that?

lateforwork

OpenAPI definition includes class hierarchy as well. You can use tools to generate TypeScript type definitions from that.

komali2

Discovering Kubb was a game changer for me last year.

HumanOstrich

Thanks for mentioning this. I always find it unsettling when I've researched solutions for something and only find a better option from a random HN comment.

Site: https://kubb.dev/

hjnilsson

Agree whole-heartedly. The strong contracts are the #1 reason to use GraphQL.

The other one I would mention is the ability to very easily reuse resolvers in composition, and even federate them. Something that can be very clunky to get right in REST APIs.

verdverm

re:#1 Is there a meaningful difference between GraphQl and OpenAPI here?

Composed resolvers are the headache for most and not seen as a net benefit, you can have proxied (federated) subsets of routes in REST, that ain't hard at all

JasonSage

> Composed resolvers are the headache for most and not seen as a net benefit, you can have proxied (federated) subsets of routes in REST, that ain't hard at all

Right, so if you take away the resolver composition (this is graph composition and not route federation), you can do the same things with a similar amount of effort in REST. This is no longer a GraphQL vs REST conversation, it's an acknowledgement that if you don't want any of the benefits you won't get any of the benefits.

8n4vidtmkvmk

Pruning the request and even the response is pretty trivial with zod. I wouldn't onboard GQL for that alone.

Not sure about the schema evolution part. Protobufs seem to work great for that.

hn_throwaway_99

> Pruning the request and even the response is pretty trivial with zod.

I agree with that, and when I'm in a "typescript only" ecosystem, I've switched to primarily using tRPC vs. GraphQL.

Still, I think people tend to underestimate the value of having such clear contracts and guarantees that GraphQL enforces (not to mention it's whole ecosystem of tools), completely outside of any code you have to write. Yes, you can do your own zod validation, but in a large team as an API evolves and people come and go, having hard, unbreakable lines in the sand (vs. something you have to roll your own, or which is done by convention) is important IMO.

FootballMuse

Pruning a response does nothing since everything still goes across the network

hdjrudni

Pruning the response would help validate your response schema is correct and that is delivering what was promised.

But you're right, if you have version skew and the client is expecting something else then it's not much help.

You could do it client-side so that if the server adds an optional field the client would immediately prune it off. If it removes a field, it could fill it with a default. At a certain point too much skew will still break something, but that's probably what you want anyway.

hn_throwaway_99

You're misunderstanding. In GraphQL, the server prunes the response object. That is, the resolver method can return a "fat" object, but only the object pruned down to just the requested fields is returned over the wire.

It is an important security benefit, because one common attack vector is to see if you can trick a server method into returning additional privileged data (like detailed error responses).

dgan

Sorry but not convinced. How is this different from two endpoints communicating through, lets say, protobuf? Both input and output will be (un)parsed only when conforming to the definition

rbalicki

The author is missing the #1 benefit of GraphQL: the ability to compose (the data for) your UI from smaller parts.

This is not surprising: Apollo only recently added support for data masking and fragment colocation, but it has been a feature of Relay for eternity.

See https://www.youtube.com/watch?v=lhVGdErZuN4 for the benefits of this approach:

- you can make changes to subcomponents without worrying about affecting the behavior of any other subcomponent,

- the query is auto-generated based on the fragment, so you don't have to worry that removing a field (if you stop using it one subcomponent) will accidentally break another subcomponent

In the author's case, they (either) don't care about overfetching (i.e. they avoid removing fields from the GraphQL query), or they're at a scale where only a small number of engineers touch the codebase. (But imagine a shared component, like a user avatar. Imagine it stopped using the email field. How many BFFs would have to be modified to stop fetching the email field? And how much research must go into determining whether any other reachable subcomponent used that email field?)

If moving fast without overhead isn't a priority (or you're not at the scale where it is a problem), or you're not using a tool that leverages GraphQL to enable this speed, then indeed, GraphQL seems like a bad investment! Because it is!

timcobb

> The main problem GraphQL tries to solve is overfetching.

this gets repeated over and over again, but if this your take on GraphQL you def shouldn't be using GraphQL, because overfetching is never such a big problem that would warrant using GraphQL.

In my mind, the main problem GraphQL tries to solve is the same "impedance mismatch" that ORMs try to solve. ORM's do this at the data level fetching level in the BE, while GraphQL does this in the client.

I also believe that using GraphQL without a compiler like Relay or some query/schema generation tooling is an anti-pattern. If you're not going to use a compiler/query generation tool, you probably won't get much out of GraphQL either.

In my opinion, GraphQL tooling never panned out enough to make GraphQL worthwhile. Hasura is very cool, but on the client side, there's not much going on... and now with AI programming you can just have your data layers generated bespoke for every application, so there's really no point to GraphQL anymore.

tcoff91

URQL and gql.tada are great client side tooling innovations.

rbalicki

If you're interested in an example of really good tooling and DevEx for GraphQL, then may I shamelessly promote this video in which I demonstrate the Isograph VSCode extension: https://www.youtube.com/watch?v=6tNWbVOjpQw

TLDR, you get nice features like: if the field you're selecting doesn't exist, the extension will create the field for you (as a client field.) And your entire app is built of client fields that reference each other and eventually bottom out at server fields.

jiggawatts

> overfetching is never such a big problem

Wait, what? Overfetching is easily one of the top #3 reasons for the enshittification on the modern web! It's one of the primary causes of incredible slowdowns we've all experienced.

Just go to any slow web app, press F12 and look at the megabytes transferred on the network tab. Copy-paste all text on the screen and save it to a file. Count the kilobytes of "human readable" text, and then divide by the megabytes over the wire to work out the efficiency. For notoriously slow web apps, this is often 0.5% or worse, even if filtering down to API requests only!

rbalicki

#1 unnecessary network waterfalls

#2 downloading the same fields multiple times

#3 downloading unneeded data/code

Checks out

switz

Hilariously – react server components largely solves all three of these problems, but developers don't seem to want to hear it.

gethly

GQL was always one of those things that sound good on the surface but in practice it never delivers and the longer you're stuck with it the worse it gets. Majority of tech is actually like this. People constantly want to reinvent the wheel but in the end, a wheel is a wheel and it will never be anything else.

gavinray

I'm probably about as qualified to talk about GraphQL as anyone on the internet: I started using it in late 2016, back when Apollo was just an alternate client-side state/store library.

The internet at large seems to have a fundamental misunderstanding about what GraphQL is/is not.

Put simply: GQL is an RPC spec that is essentially implemented as a Dict/Key-Value Map on the server, of the form: "Action(Args) -> ResultType"

In a REST API you might have

  app.GET("/user", getUser)
  app.POST("/user", createUser)
In GraphQL, you have a "resolvers" map, like:

  {
    "getUser": getUser,
    "createUser": createUser,
  }
And instead of sending a GET /user request, you send a GET /query with "getUser" as your server action.

The arguments and output shape of your API routes are typed, like in OpenAPI/OData/gRPC.

That's all GraphQL is.

andrewingram

As someone who’s used GraphQL since mid-2015, if you haven’t used GraphQL with Relay you probably haven’t experienced GraphQL in a way that truly exploits its strengths.

I say probably because in the last ~year Apollo shipped functionality (fragment masking) that brings it closer.

I stand by my oft-repeated statement that I don’t use Relay because I need a React GraphQL client, I use GraphQL because I really want to use Relay.

The irony is that I have a lot of grievances about Relay, it’s just that even with 10 years of alternatives, I still keep coming back to it.

maxcan

Can you elaborate? I've used URQL and Apollo with graphql code gen for type safety and am a big fan.

What about relay is so compelling for you? I'm not disagreeing, just genuinely curious since I've never really used it.

tcoff91

Try gql tada it’s much better than graphQL codegen

jayd16

This seems a bit reductive as it skims over the whole query resolution part entirely.

verdverm

Which is where the real complexity comes in

thom

This, for me, is a perfect description of the entirety of GraphQL tbh.

8n4vidtmkvmk

I think you're oversimplifying it. You've left on the part where the client can specify which fields they want.

verdverm

That's something you should only really do in development, and then cement for production. Having open queries where an attacker can find interesting resolver interactions in production is asking for trouble

fgkramer

But has this been thoroughly documented and are there solid libraries to achieve this?

My understanding is that this is not part of the spec and that the only way to achieve this is to sign/hash documents on clients and server to check for correctness

hdjrudni

Sure, maybe you compile away the query for production but the server still needs to handle all the permutations.

ericyd

Is this relevant to the posted article? I don't see how the OP misrepresents anything about GQL.

mirekrusin

Except you can't have ie. union as argument, which means you can't construct ie. SQL/MongoDB-like where clauses.

verdverm

I have strong agreement here and would add reasoning about auth flow through nested resolvers is one of the biggest challenges because it adds so much mental overhead. The reason is that a resolver may be called through completely different contexts and you have to account for that

The complexity and time lost to thinking is just not worth it, especially once you ship your GarphQL app to production, you are locking down the request fields anyway (or you're keeping yourself open for more pain)

I even wrote a zero-dependency auth helpers package and that was not enough for me to keep at it

https://github.com/verdverm/graphql-autharoo

Like OP says, pretty much everything GraphQL can do, you can do better without GraphQL

cluckindan

Have you tried using a decorator for auth?

Also, using a proper GraphQL server and not composing it yourself from primitives is usually beneficial.

verdverm

This was an auth extension or plugin for Apollo, forget what they called it.

Apollo shows up in the README and package.json, so I'm not sure why you are assuming I was not using a proper implementation

nmilo

This doesn’t really make sense. Obviously if you combine GQL with BFF/REST you’re gonna have annoying double-work —- you’re solving the same problem twice. GQL lets you structure your backend into semantic objects then have the frontend do whatever it wants without extra backend changes. Which lets frontend devs move way faster.

aabhay

How do GraphQL based systems solve the problem of underlying database thrashing, hot shards, ballooning inner joins, and other standard database issues? What prevents a client from writing some adversarial-level cursed query that causes massive internal state buildup?

I’m not a database neckbeard but I’ve always been confused how GraphQL doesn’t require throwing all systems knowledge about databases out the window

null

[deleted]

spooneybarger

Most servers implement a heuristic for "query cost/complexity" with a configurable max. At the time the query is parsed, its cost is determined based on the heuristic and if it is over the max, the query is rejected.

lll-o-lll

Which would be fine for internal facing, but it doesn’t sound like it would be enough in an adversarial context?

spooneybarger

There are a lot of public facing graphql servers that use it without issue other than frustrating users of non adversarial but complex requirements. The problem is that it is generally on a per request basis.

An adversary is going to utilize more than a single query. It mostly protects against well intentioned folks.

Other forms of protection such as rate limiting are needed for threat models that involve an adversary.

The same problems exist with REST but there it is easier as you can know query complexity ahead of time at end points. GraphQL has to have something to account for the unknown query complexity, thus the additional heuristics.

trashymctrash

What I liked about GraphQL was the fact that I only have to add a field in one place (where it belongs in the schema) and then any client can just query it. No more requests from Frontend developers like „Hey, can you also add that field to this endpoint? Then I don’t have to make multiple requests“. It just cuts that discussion short.

I also really liked that you can create a snapshot of the whole schema for integration test purposes, which makes it very easy to detect breaking changes in the API, e.g. if a nullable field becomes not-nullable.

But I also agree with lots of the points of the article. I guess I am just not super in love with REST. In my experience, REST APIs were often quite messy and inconsistent in comparison to GraphQL. But of course that’s only anecdotal evidence.

matsemann

But the first point is also its demise. I have object A, and want to know something from a related object E. Since I can ask for A-B-C-D-E myself, I just do it, even though the performance or spaghettiness takes a hit. Then ends up with frontend that's tightly coupled to the representation at the time as well, when "in the context of A I also need to know E" could've been a specialized type hiding those details.

Culonavirus

> No more requests from Frontend developers like „Hey, can you also add that field to this endpoint? Then I don’t have to make multiple requests“.

Do people actually work like this is 2025? I mean sure, I guess when you're having entire teams just for frontends and backends then yea, but your average corporate web app development? It's all full stack these days. It's often expected that you can handle both worlds (client and server) and increasingly its even TypeScript "shared universe" when you don't even leave the TS ecosystem (React w/ something like RR plus TS BFF w/ SQL). This last point, where frontend and backend meet, is clearly the way things are going in general. I mean these days React doesn't even beat around the bush and literally tells you to install it with a framework, no more create-react-app, server side rendering is a staple now and server side components are going to be a core concept of React within a few years tops.

Javascript has conquered the client side of the internet, but not the server side. Typescript is going to unify the two.

roscue

I would agree that REST beats GraphQL in most cases regarding complexity, development time, security, and maintainability if the backend and frontend are developed within the same organization.

However, I think GraphQL really shines when the backend and frontend are developed by different organizations.

I can only speak from my experience with Shopify's GraphQL APIs. From a client-side development perspective, being able to navigate and use the extensive and (admittedly sometimes over-)complex Shopify APIs through GraphQL schemas and having everything correctly typed on the client side is a godsend.

Just imagining offering the same amount of functionality for a multitude of clients through a REST API seems painful.

imperio59

GraphQL was created to solve many different problems, not just overfetching.

These problemes at the time generally were: 1) Overfetching (yes) from the client from monolithic REST APIs, where you get the full response payload or nothing, even when you only want one field

2) The ability to define what to fetch from the CLIENT side, which is arguably much better since the client knows what it needs, the server does not until a client is actually implemented (so hard to fix with REST unless you hand-craft and manually update every single REST endpoint for every tiny feature in your app). As mobile devs were often enough not the same as backend devs at the time GraphQL was created, it made sense to empower frontend devs to define what to fetch themselves in the frontend code.

3) At the time GraphQL was invented, there was a hard pivot to NoSQL backends. A NoSQL backend typically represents things as Objects with edges between objects, not as tabular data. If your frontend language (JSON) is an object-with-nested-objects or objects-with-edges-between-objects, but your backend is tables-with-rows, there is a mismatch and a potentially expensive (at Facebook's scale) translation on the server side between the two. Modeling directly as Objects w/ relationships on the server side enables you to optimize for fetching from a NoSQL backend better.

4) GraphQL's edges/connections system (which I guess technically really belongs to Relay which optimizes really well for it) was built for infinitely-scrolling feed-style social media apps, because that's what it was optimized for (Facebook's original rewrite of their mobile apps from HTML5 to native iOS/Android coincided with the adoption of GraphQL for data fetching). Designing this type of API well is actually a hard problem and GraphQL nails it for infinitely scrolling feeds really well.

If you need traditional pagination (where you know the total row count and you want to paginate one page at a time) it's actually really annoying to use (and you should roll your own field definitions that take in page size and page number directly), but that's because it wasn't built for that.

5) The fragment system lets every UI component builder specify their own data needs, which can be merged together as one top-level query. This was important when you have hundreds of devs each making their own Facebook feed component types but you still want to ensure the app only fetches what it needs (in this regard Relay with its code generation is the best, Apollo is far behind)

There's many other optimizations we did on top of GraphQL such as sending the server query IDs instead of the full query body, etc, that really only mattered for low-end mobile network situations etc.

GraphQL is still an amazing example of good product infra API design. Its core API has hardly changed since day 1 and it is able to power pretty much any type of app.

The problems aren't with GraphQL, it's with your server infra serving GraphQL, which outside of Facebook/Meta I have yet to see anyone nail really well.

hashmap

> GraphQL isn’t bad. It’s just niche. And you probably don’t need it.

> Especially if your architecture already solved the problem it was designed for.

What I need is to not want to fall over dead. REST makes me want to fall over dead.

> error handling is harder than it needs to be GraphQL error responses are… weird. > Simple errors are easier to reason about than elegant ones.

Is this a common sentiment? Looking at a garbled mash of linux or whatever tells me a lot more than "500 sorry"

I'm only trying out GraphQL for the first time right now cause I'm new with frontend stuff, but from life on the backend having a whole class of problems, where you can have the server and client agree on what to ask for and what you'll get, be compiled away is so nice. I don't actually know if there's something better than GraphQL for that, but I wish when people wrote blogs like this they'd fill them with more "try these things instead for that problem" than simply "this thing isn't as good as you think it is you probably don't need it".

Dibes

If isomorphic TS is your cup of tea, tRPC is a nicer version of client server contracting than graphql in my opinion. Both serve that problem quite well though.