GraphQL is an amazing API query language. It allows you to expose your data to the front-end as a graph. That's extremely powerful because graphs are amazing at representing relationships between your domain entities.
However you need to be careful. Given how much GraphQL does for you, you might not realise how expensive some queries can be.
Time for an example
Query {
todos {
title
assigned_to: {
first_name
}
}
}
A naive implementation of the query resolver:
/*
Assume 2 MongoDB collections todos and users.
todo schema: { _id, title, assigned_to_id }
user schema: { _id, first_name }
*/
const TodosQueryResolver = () => {
return Todos.find({})
}
const AssignedToFieldResolver = (todo) => {
return Users.findOne({_id: todo.assigned_to_id})
}
Why is this naive?
Assume you have a super busy user who has a 10,000 todos. Every time the query is called you will have 10,001 calls to your database. Ouch.
So what do you do?
Pagination! If you allow max 10 todos to be returned then you will have 11 db calls per query. No ouch.
Why is pagination the solution to the problem above?
Let's go through the alternatives first.
Alternative 1: Change the GraphQL schema
So instead of:
type Todo {
_id: String
title: String
assigned_to: User
}
You do:
type Todo {
_id: String
assigned_to_id: String
}
On the client you'll do 2 queries:
- fetch the todos
- fetch the users
That's a bad approach because you remove the power of GraphQL! You effectively use your GraphQL API as a REST API.
Alternative 2: optimise the GraphQL resolvers
That would be premature optimisation. Also you will need to maintain that optimised code and it will only be useful for this specific query.
Conclusion:
pagination wins over the alternatives because:
- keeps the relationships between your domain entities (e.g., users, todos ...)
- Easy to implement (no fancy db query optimisations)
- Pattern you can extract and apply to all your list queries