This seems a little pedantic. It may not be literally required by the technology, but GraphQL certainly makes it easier to introduce N+1 queries than a traditional REST API.
If anything I'd say REST is dramatically worse in this respect. There's no structure for nested queries (or anything except thing/:id really), so the only broadly compatible option is to pull every piece as a separate GET request.
Sure, you can use query params... but there's no implied support nor semantics, so one site will do one thing and another will do something from a completely different universe of architectural patterns, while a third will just have nothing, and there's no way to reconcile those in a consistent way.
You don't need to have 1:1 equivalence between API resources and database models, though. For example, you can create an API resource called TweetActivity, and then the backend code for GET /tweet-activity could put together a database query that grabs all the tweet likes, comments and basic commenter info (name, profile image), from different database tables, into a single new object. You can give that object the same ID as the tweet itself, and you can put a cache layer around that endpoint to, for example, save that response for the next 1 minute.
That being said, one thing you give up is providing a standard way for the client to specify which fields it needs returned. For example, a client might want to dig deeper into the commenter profile info. GraphQL's resolvers architecture opens up that possibility immediately.
Agreed on pretty much all points, but you've just described a way to optimize an N+1 system without changing the fundamental N+1 aspect. The caller is still making N+1 requests and incurring round-trip latencies (end-to-end cost can at best be reduced to 2 with parallelism), and the server still has to receive N+1 requests. REST has no semantics to change this.
In a GraphQL API backed by a single database you could write resolvers that analyze the entire query and rephrase it as a single database query. But I'm not aware of anyone doing this. The common/straight forward solutions resolve each layer after each other, which you can optimize to 1 DB query per layer of the GraphQL query using dataloader patterns, with further optimizations for known common patterns.
Some people do try to generate a single SQL query that covers all nested resolves. See Join Monster [1]. I'm skeptical that it would ever work well in SQL.
Datomic, being close to a graph database, makes constructing a single deep query for all resolvers fairly straight forward. This is the approach my team is taking now. It's worked quite a bit better than my DataLoader biased intuition suggested.
I have, for a bespoke internal application. PostgreSQL is actually capable of expressing GraphQL queries as (rather elaborate) SQL queries. You end up generating queries that use a lot of LATERAL sub-selects. The risk is generating queries with poor performance, and that risk is high enough that I don't think this is a viable approach for complex applications.
Yeah, I think there is a perception that people want direct queries into their database, but that is actually not the correct way to think about graphql.
A straightforward implementation certainly would have that problem. You have to put a decent amount of effort in to reduce query count to fill a nested query graph.
Not really. A straightforward implementation has nothing to do with database usage. A resolver is an endpoint. You can leverage that like you can in any other http backend server.
Nested resolvers can incur extra queries as you follow down the tree, unless you put extra effort into pre-fetching what child nodes need, or some kind of data loader as GP suggested.
query {
parent {
child {
field
}
}
}
A straightforward, naive implementation would resolve `parent`, then a resolver for `child` would execute, then for `field`. If `child` is a table linked by an FK, a second query would be executed, unless you pre-fetch the results through a join when resolving `parent`.