GraphQL: Letting the Client Define the Shape of Data
- date
- category
- Backend
- also in
- Frontend · System Design
- reading
- 2 min / 373 words
REST usually ties the response to a resource.
RPC ties it to an operation.
GraphQL ties it to the client's query.
query {
user(id: 42) {
name
orders {
id
total
}
}
}
The client defines the shape of the data it needs.
One endpoint, many queries
A typical request looks like this:
POST /graphql
Content-Type: application/json
The body contains a GraphQL document.
HTTP no longer says:
which resource are we fetching?
That is described by the GraphQL layer.
HTTP
|
v
GraphQL document
|
v
schema
|
v
execution
The schema is the contract
For example:
type User {
id: ID!
name: String!
orders: [Order!]!
}
type Query {
user(id: ID!): User
}
The schema defines a graph of types and the operations available to the client.
A query is validated against the schema before execution.
That gives a very strong contract and excellent tooling.
Resolvers
Every field can be resolved by backend code.
Simplified:
Query.user
|
v
User.orders
|
v
Order.total
That allows composing data from many sources without exposing that structure to the client.
But it leads to one of the classic GraphQL problems.
N+1
Suppose:
query {
users {
orders {
id
}
}
}
A naive implementation may perform:
1 query -> users
N queries -> orders for each user
That is N+1.
GraphQL does not cause it automatically, but its resolver model exposes such a problem very easily.
The typical solution is batching and request-scoped caching, for example through a DataLoader-like pattern.
Flexibility has a price
In REST the backend knows the cost of an endpoint well in advance.
In GraphQL the client can build many different query trees.
So there are things that have to be controlled:
- depth,
- complexity,
- pagination,
- expensive fields,
- authorization per field,
- query limits.
The API becomes more flexible, but its execution becomes less predictable.
Caching is harder too
The HTTP cache understands this well:
GET /users/42
It copes far worse with:
POST /graphql
where the same URL can represent thousands of different queries.
GraphQL usually moves a bigger part of caching into the client application layer.
The client can normalize data by identifiers:
User:42
Order:17
Order:18
That is powerful, but again: more flexibility means more mechanism on the application side.
REST, RPC or GraphQL?
There is no hierarchy:
REST < RPC < GraphQL
These are different models.
REST
-> resources and HTTP semantics
RPC
-> operations and a contract of procedures
GraphQL
-> a data graph and a query language
All of them can use the same HTTP transport and all of them can be called with fetch().
That is the most important conclusion of the whole series:
Fetch = browser API
HTTP = protocol
REST / RPC = API architecture
GraphQL = application query protocol
These layers solve different problems.
Mixing them leads to arguments like "GraphQL vs HTTP" or "fetch vs REST", which compare things from different levels of abstraction.
Only once those layers are separated can frontend to backend communication be designed sensibly.