N+1 Problem with GraphQL

This post carries on from my previous and first post about Auth with GraphQL. The examples below are used with express-graphql.

Make efficient queries (N+1 Problem)

So when you start nesting your queries you’ll soon find out that GraphQL is not very efficient and makes database requests for each nested query.

For example, if you have a list of students, each student can have a number of classes. You make 1 request for the students which gives us the N number, which is the number of requests for each the student classes to fetch.

1 request for students returns N students.
N requests for student classes.
// Nested query example
query getStudents {
studentName
...
classes {
className
...
}
}

You can easily see that this is a problem if you were request many students, not to forget additional nested queries.

A solution to this problem is the DataLoader!

DataLoader is a generic utility to be used as part of your application’s data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching.

So were going to batch the unique key/id for each student returned and then make a single request to get the classes for those students.

In this example we will require the DataLoader file in our resolver file instead of passing it through the context object.

First we create the classes-loader.js file and add the following lines of code. Here, the batch function receives the ids , makes a db request and then groups the returned data to match the order of the ids as they were passed in to be returned.

const DataLoader = require('dataloader')const batchStudentIds = async ids => {
// Logging batched student ids
console.log(ids)

// Get all classes by student id
const classes = studentRepo().getClassesByStudentId(ids)

// Now group the classes by the ids as they're passed into the
batch function
const groupedById =
ids.map(id => classes.filter(c => c.studentId === id))
return Promise.resolve(groupedById)
}
module.exports = new DataLoader(batchStudentIds, { cache: false })

Next, in our resolver, require the classesDataLoader file and reference it inside the classes function under the Student object.

const classesDataLoader = require('path/to/file')Query: {...},
Student: {
// Calls "classes" on type Student
classes: (student, args, context, info) =>
classesDataLoader.load(student.id)
},
...

That’s it 👍 you can now query students and classes more efficiently.

Once you’ve made the initial request for say 10 students, the DataLoader caches the response. If you now execute the same query but return 11 students the DataLoader would only make a request for the 1 student which was not cached. Caching is optional and true by default.

I recommend reading the DataLoader documentation to get more insight.

I’ve created this Gist which shows it working. Paste it in your favourite editor to test yourself.

I’ve also added this to my express-graphql-api project on GitHub.

Happy coding!

Lead Developer. Angular. Node. GraphQL. JavaScript enthusiast.