It's not a replacement for SQL. It's a replacement for REST. And the intent is to provide a statically typed, holistic interface and encourage best practices in API design. It's also quite nice because there are clients that plug into it and offer substantial benefits as a result, like Relay.
There are lots of ways people write resolution methods for GraphQL, and this article provides a good number of them. You can think of them as query optimization approaches if it helps. But it's not quite like SQL, where in SQL you say whatever you like and it just gets figured out. GraphQL forces an amount of intention and asks schema writers to consider the most concrete use cases possible rather than just enabling super generic ones.
GraphQL lets you write queries for objects. This fundamentally changes how you architect your app and database.
In a REST API, you turn known a finite list of known query patterns (endpoints) into DB queries, which you can test, hand-write, and optimize.
If you let end users query your object model, it takes far more work to ensure they don't do something very expensive.
Seems like it postpones premature optimisation... which is a good thing in my opinion
But, even should you have to support a complex schema, the fine article showcases a number of great mitigations that cover basically every possible issue.
The only issue that I don't think is covered here is that collecting all this data up and sending it all at once can sometimes be slow or even time out, and there's no mechanism really to allow GraphQL to defer the collection of some fields until they're ready. It's coming very soon (in the form of @defer; to the spec, to graphql-js, to Relay, and to others) but it's not quite here yet.
The article assumes simple schemas as well - a parent with many child relationships in this article could lead to some complex resolvers. Unless I misunderstand the article many of the strategies here are in ORMs (e.g. lazy subselects) which often don't perform as well as a crafted query given the still multiple roundtrip's to the db. They also require more code, and exhibit branching which means that performance is a function of the users query. If you know your user that's fine, but then again I don't see the advantage vs a straight REST or RPC call which would result in simpler code anyhow. It seems to work IMO in cases I guess when service isn't critical, your model/schema is simple, or you prefer flexibility of data access over reliability/predictability.
How so? There are existing tools that prevent both N+1 problems (Dataloader) and complex, recursive queries (depth and complexity limits).
If your GraphQL schema requires expensive queries because it has a different schema than your database, is it really decoupled?
It's much easier to evolve from a very restrictive schema to a more open schema as the use-cases present themselves, than it is to move clients from an open schema to a more restrictive one because your backend is choking trying to support the cardinality of edge cases introduced by it.
GraphQL provides one-way (server-bound) run-time type assertions.
I constantly see developers that should know better confuse validation with type checking. I've had developers, in earnest, ask how they can use TypeScript on run-time data inputs. I have to break the bad news to them and simply tell them: no, you can't do that. That's not how type systems work.
Out of the box, GraphQL provides no validation, that I know of, beyond the elementary data types. Anyone that knows a thing about XSS or SQL-injection can tell you that a string is not always just a string. Anyone using C/C++ that has dealt with stack overflows and code injection can tell you that an integer is not always an integer. Sometimes it's a pointer. Oops.
You're going to hand code validations, each and every time. Because no tool knows how many characters your DB is setup to handle for a username, for example. GraphQL and TypeScript won't save you.
GraphQL tooling cannot literally prevent JSON from having a string value where there's supposed to be a number value, but it can ensure that application code never has runtime type errors. Is that "validation rather than type checking"? Sure, I suppose so, but my application code that consumes GraphQL responses will either get some representation of an error, or a representation of a successful response that is guaranteed to have the types I expect.
And yes, there are plenty of other data formats and tools that provide the same sort of thing: some representation of a schema, some non-application code that checks runtime data against that schema, and a contract with the application code that each response will be either an error or a type-safe successful response.
Queries over GraphQL often wind up being handled by custom resolvers and being about the same as remote procedure calls. It is kind of neat how it separates the queries from the mutations. Blitz does that, but with GraphQL being optional:
https://blitzjs.com/docs/query-resolvers
gRPC just provides an efficient transport and has the client library provide the interface. It works out pretty well for google APIs.
The ruby graphql ecosystem is also lacking for decent loaders, but such a loader would likely not integrate with ActiveRecord.
Also, remember, GraphQL is a language, you're responsible for locating the data on the backend.
Would love to hear what's been missing / painful and factor that into the roadmap.