gotchatypescriptgraphqlCritical
N+1 problem in GraphQL resolvers — use DataLoader to batch and cache
Viewed 0 times
dataloader 2.x
N+1DataLoaderbatchcacheresolverdatabase queriesperformance
Problem
Every resolver that fetches a related entity triggers a separate database query. For a list of 100 posts each resolving their author, you get 101 queries: 1 for posts, 100 for authors. This is the N+1 problem and silently destroys performance at scale.
Solution
Use DataLoader to batch all individual key lookups into a single batched query per tick of the event loop.
import DataLoader from 'dataloader';
// Create a loader per request (not globally)
const userLoader = new DataLoader(async (userIds: readonly string[]) => {
const users = await db.user.findMany({ where: { id: { in: [...userIds] } } });
// Must return results in same order as keys
return userIds.map(id => users.find(u => u.id === id) ?? null);
});
// In resolver
const Post = {
author: (post, _args, ctx) => ctx.loaders.user.load(post.authorId),
};
// Add loaders to context so they're shared per request
const server = new ApolloServer({
context: () => ({
loaders: { user: createUserLoader() },
}),
});Why
DataLoader coalesces all
.load() calls within a single event loop tick into one batch call. It also caches results within the request lifetime, so requesting the same id twice only hits the database once.Gotchas
- DataLoader instances must be created per-request, NOT as singletons — otherwise cache leaks across users
- The batch function must return results in the SAME ORDER as the input keys array
- Missing keys must be returned as null or an Error, not omitted from the result array
- DataLoader only batches within a single event loop tick — async work between loads breaks batching
Code Snippets
Minimal DataLoader with ordering guarantee
const userLoader = new DataLoader<string, User | null>(async (ids) => {
const rows = await db.query('SELECT * FROM users WHERE id = ANY($1)', [[...ids]]);
return ids.map(id => rows.find(r => r.id === id) ?? null);
});Context
Any GraphQL API that resolves related entities in list queries
Revisions (0)
No revisions yet.