Lesson 15 / 25
Solving N+1 With DataLoader
Batching and per-request caching.
Collect keys, load once
Naive resolvers cause the N+1 problem: fetching 50 orders, then calling the product resolver for each item, triggers dozens of separate database queries. DataLoader (and equivalents in other languages) collects all keys requested during one tick of execution, calls a batch function once with every key (for example WHERE id IN (...)), and caches results for the rest of the request. Create new loaders per request so caches never leak between users.
A product loader
JavaScript using the dataloader package (a sketch).
import DataLoader from "dataloader";
function createLoaders(db) {
return {
productById: new DataLoader(async (ids) => {
const rows = await db.query("SELECT * FROM products WHERE id = ANY($1)", [ids]);
const byId = new Map(rows.map((r) => [String(r.id), r]));
return ids.map((id) => byId.get(String(id)) ?? null); // same order as keys
}),
};
}
// context: ({ req }) => ({ loaders: createLoaders(db), ... })
const resolvers = {
OrderItem: {
product: (item, _args, { loaders }) => loaders.productById.load(item.productId),
},
};Return results in key order
The batch function must return one result per key, in the same order, using null for missing ones.
Quick check: What does DataLoader do?
- Batches many individual loads into one call and caches them per request
- Encrypts queries
- Generates the schema
- Replaces the database
Answer
Batches many individual loads into one call and caches them per request — Fixes N+1 queries.