From the consumer side I really enjoy using it. Queries can avoid lots of network chatter and mutations embrace procedural thinking (REST has answers CRUD, but not everything a server needs to do is resource based).
However, as a server dev I really don't like it. It puts much more power into the consumer's hands without providing good visibility into the database queries it's making behind the scenes and how heavy those might be.
As I see it, it trades client side complexity for some loss of control over database queries. I often see it sold as the former without mentioning the latter.
I don't particularly like the error handling. You can get 200 OK responses with errors in them, and I have seen that not handled in reviews two times.
REST is still great, and it's hard to beat for simplicity, but I also don't consider it to be XOR with GQL. In systems that I already have GQL set up for and for which REST is appropriate, I like to actually build REST APIs as convenience wrappers on top of my GQL underpinnings - rather than a controller action being a set of authorizations, record loads, and transformations to JSON, I just execute a GQL query (in server code) against my schema and consume the result to transform it to the desired output. In practice, this is often what REST APIs are doing ad hoc, but being able to leverage all the guarantees provided by the GQL schema results in very simple, straightforward, flexible implementations.
GQL represents a large shift in how you think about the structuring of an application's API, but done well I consider it flatly superior for systems beyond the trivial because of the reduction in maintenance friction.
But as a general opinion, I admire the theoretical elegance of GraphQL (only return the exact data needed), but using GraphQL is probably more trouble than it's worth for most projects.
If there is any significant latency in accessing your backing data store, as is usually the case with a SQL database, then care is needed when implementing your GraphQL server. Naive implementations will have the N+1 problem (or the "N²+N+1" problem, etc) and will be slow.
I haven't found a GraphQL server library which I like, so I implemented my own. (I found an MIT-licensed query parser, which saved some work.)
It is designed to make no more than one SQL query for each branch node in the GraphQL query AST (assuming that each type of entity is stored in a single table). Running in production for a while now, works great, fast, was fun to write.
I like how as a backend engineer you can just publish all the data that makes sense in the graphql schema and the frontend folks can just grab whatever they want in a single request to populate whatever view they're working on - easy to explore with graphiql, and you can introspect the schema to generate types for typescript...
I feel like REST is shoehorned on top of HTTP, which was never designed for that. It has some advantages such as accessing features from the HTTP toolchain, like easy caching and many developer tools, but I think GraphQL is more adapted as an API layer unless you want a tiny CRUD API for one resource.
Also I think GraphQL schemas are easier to work with than Swagger/OpenAPI specifications.
You have many other alternatives too, such as SOAP, CORBA, or gRPC, but I would prefer GraphQL unless an alternative is a lot more adapted to my problem. It's still JSON and HTTP, which makes it easy to work with, and it's powerful enough.
When you are defining some component and you can define exactly the query that the component will need in the same place, that is great.
However, doing normal things like file uploads introduce clunky extra steps like sending a mutation to get back a url that you then make the upload request to. Clunky.
Further, in the past my team has experimented with trying to do multi-model transactions with I think Apollo graph ql library, and it was not readily possible. That was back in 2020, maybe things have improved since then?
From a client perspective it's night and day, server implementation is a little more tricky but the C# libraries seem to avoid a lot of the pain other back-end answers seem to be encountering, not sure why that should be.
From the front end perspective it's really great. I like how flexible the querying is and how concise it makes the comm code.
From the backend perspective, I'm not sure yet how to properly implement authentication; I haven't yet touched mutability. It seems more complicated than REST, but I wonder if it's because I'm very new with this.
Ultimately I'm sure there are gonna be rough edges, but as a consumer it's a very pleasant experience.
I think it's a result of OpenAPI being a very large spec that supports constructs that most languages don't deal with properly (like a field being able to be a number or an object), but a lot of OpenAPI code generators just don't work reliably.
GraphQL isn't perfect in this regard - a lot of languages don't really support union types - but I've found GraphQL client code generators to be more reliable in practice.