Implementing DataLoader Pattern

1. Understanding N+1 Problem

Query: posts { author { name } }

Without DataLoader:
  1 query for posts
+ N queries (one per post.author)
= N+1 problem

With DataLoader:
  1 query for posts
+ 1 batched query for all unique authorIds
= 2 queries total
      

2. Creating DataLoader Instance

Example: Basic loader

import DataLoader from "dataloader";

const userLoader = new DataLoader(async (ids) => {
  const users = await db.user.findMany({ where: { id: { in: ids } } });
  const map = new Map(users.map(u => [u.id, u]));
  return ids.map(id => map.get(id) ?? null);
});
OptionDefaultDetail
batchtrueGroup calls in same tick
cachetrueCache by key per loader
maxBatchSizeInfinityCap batch size
cacheKeyFnidentityHash function

3. Implementing Batch Function

RuleDetail
InputArray of unique keys
OutputArray of values, SAME length, SAME order
MissesReturn null/undefined or Error in slot
AsyncReturns Promise<Array<V|Error>>

4. Using DataLoader in Resolver

Example: Loader in resolver

Post: {
  author: (post, _, { loaders }) => loaders.userById.load(post.authorId)
}

5. Batching Database Queries

Example: SQL IN batch

const batchUsers = async (ids) =>
  db("users").whereIn("id", ids).then(rows => {
    const map = new Map(rows.map(r => [r.id, r]));
    return ids.map(id => map.get(id) ?? null);
  });

6. Caching Loaded Data

ScopeLifetime
Per-requestDefault; safe
Cross-requestRisky — stale auth/permissions
Redis-backedCustom cacheMap

7. Clearing DataLoader Cache

MethodEffect
.clear(key)Invalidate one entry
.clearAll()Reset entire cache
After mutationsClear affected entries

8. Priming DataLoader Cache

Example: Prime after fetch

const posts = await db.post.findMany({ include: { author: true } });
posts.forEach(p => loaders.userById.prime(p.author.id, p.author));
return posts;

9. Handling Load Errors

StrategyDetail
Return Error in slotload() rejects only for that key
Throw in batch fnALL pending loads reject
Silent missReturn null for not-found (recommended)

10. Creating Multiple Loaders

LoaderKey
userByIdUser id → User
postsByAuthorauthorId → [Post]
commentsByPostpostId → [Comment]
tagsByPostpostId → [Tag]

11. Using Loader with Foreign Keys

Example: One-to-many loader

const postsByAuthor = new DataLoader(async (authorIds) => {
  const rows = await db.post.findMany({ where: { authorId: { in: authorIds } } });
  const grouped = new Map(authorIds.map(id => [id, []]));
  rows.forEach(p => grouped.get(p.authorId).push(p));
  return authorIds.map(id => grouped.get(id));
});