DataLoader & N+1 Problem
The N+1 problem is GraphQL's most notorious performance pitfall. When fetching a list of items each with nested data, naive resolvers fire one database query per item. DataLoader batches and caches these calls into a single query.
1 The N+1 Problem Illustrated
GraphQL Query that causes N+1
# This innocent-looking query causes N+1 database queries
{
posts { # 1 query: SELECT * FROM posts
title
author { # N queries: SELECT * FROM users WHERE id = ?
name # One query per post!
}
}
}
# With 100 posts: 1 + 100 = 101 database queries!
# With DataLoader: 1 + 1 = 2 database queries
2 Fixing with DataLoader
JavaScript — DataLoader Setup
const DataLoader = require("dataloader");
// Batch function — receives array of IDs, returns array of results
async function batchUsers(userIds) {
// ONE query for all IDs at once
const users = await User.find({ _id: { $in: userIds } });
// Must return in same order as input IDs!
const userMap = {};
users.forEach(u => { userMap[u.id] = u; });
return userIds.map(id => userMap[id] || null);
}
// Create per-request DataLoader (never share between requests!)
function createLoaders() {
return {
userLoader: new DataLoader(batchUsers),
commentLoader: new DataLoader(batchCommentsByPostId)
};
}
// In Apollo Server context
context: async ({ req }) => ({
currentUser: await getUser(req),
loaders: createLoaders() // Fresh loaders per request
});
// In resolver — uses loader instead of direct DB call
const resolvers = {
Post: {
author: (post, _, { loaders }) => {
return loaders.userLoader.load(post.authorId); // batched automatically!
}
}
};
3 Code Challenge
Challenge: Add a
commentLoader DataLoader that batches comment fetching by postId. Use it in the Post.comments resolver. Compare the number of DB queries before and after (use Mongoose's debug mode to see queries).