I found myself spending a large amount of time inventing and trying to patch in solutions that most RPC and REST frameworks solved long ago for both the server AND the client (auth, rate limiting, error handling and validation stick out particularly). Client solutions are comparatively heavy, complicated, and riddled with gotchas (e.g. caching) that trip up new team members more than REST. It’s not impossible to build performant GraphQL solutions, but the solutions feel more like afterthoughts and require more vigilance to ensure your team doesn’t stick their finger in an electrical socket compared to REST. The lack of namespacing or organization results in almost unintelligible query and mutation documentation for large projects. The comparatively large size and complexity of requests can be a headache for ops teams. I loathe that interfaces and inheritance don’t work for mutations. Front end devs just use it like a very heavy REST and the holy grail promised by stuff like Relay never materializes. I could go on.
And at the end of the day, the app’s API usage will stabilize and mature, and the expressiveness becomes less compelling compared to its cost. When I went back to OpenAPI and REST, it was like a breath of fresh air, I felt I was building things much faster. I will grant you that generating clients from OpenAPI still is the worst part.