DataLoader helps reduce repeated database calls by batching and caching requests. It makes your NestJS app faster and more efficient.
DataLoader integration in NestJS
import DataLoader from 'dataloader'; const loader = new DataLoader(async (keys) => { // batch load function returns results in order of keys const results = await batchFetchFromDatabase(keys); return keys.map(key => results.find(r => r.id === key)); });
The batch load function receives an array of keys and must return results in the same order.
DataLoader caches results during a request to avoid duplicate fetches.
const userLoader = new DataLoader(async (userIds) => { const users = await userService.findByIds(userIds); return userIds.map(id => users.find(user => user.id === id)); });
const postLoader = new DataLoader(async (postIds) => { const posts = await postService.findByIds(postIds); return postIds.map(id => posts.find(post => post.id === id)); });
This example shows a NestJS service using DataLoader to batch user ID requests. It caches during one request scope and returns user names in order.
import { Injectable, Scope } from '@nestjs/common'; import DataLoader from 'dataloader'; interface User { id: number; name: string; } @Injectable({ scope: Scope.REQUEST }) export class UserLoader { public readonly loader: DataLoader<number, User>; constructor(private readonly userService: UserService) { this.loader = new DataLoader(async (userIds: readonly number[]) => { const users = await this.userService.findByIds(userIds as number[]); return userIds.map(id => users.find(user => user.id === id)); }); } } // Usage in resolver or service async function getUserNames(userLoader: UserLoader, ids: number[]) { const users = await Promise.all(ids.map(id => userLoader.loader.load(id))); return users.map(user => user?.name ?? 'Unknown'); } // Mock UserService for demonstration class UserService { private users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Carol' }, ]; async findByIds(ids: number[]): Promise<User[]> { // Simulate async DB call return this.users.filter(user => ids.includes(user.id)); } } // Demo run (async () => { const userService = new UserService(); const userLoader = new UserLoader(userService); const names = await getUserNames(userLoader, [1, 2, 3]); console.log(names.join(', ')); })();
Use request-scoped providers in NestJS to create a new DataLoader instance per request.
Always return results in the same order as the keys array to avoid mismatches.
DataLoader caches only during one request; it does not cache across requests.
DataLoader batches and caches data fetching to improve performance.
Use it in NestJS with request-scoped providers for safe caching.
Always keep the batch function output order matching the input keys.
Practice
DataLoader in a NestJS application?Solution
Step 1: Understand DataLoader's role
DataLoader is designed to batch and cache data fetching operations to reduce redundant calls.Step 2: Compare with other options
Options A, C, and D relate to other NestJS features, not DataLoader's purpose.Final Answer:
To batch and cache multiple data requests to improve performance -> Option DQuick Check:
DataLoader = batch + cache [OK]
- Confusing DataLoader with routing or middleware
- Thinking DataLoader manages database migrations
- Assuming DataLoader serves static files
Solution
Step 1: Recall DataLoader instantiation syntax
DataLoader is instantiated withnew DataLoader(batchFunction), where batchFunction is a function receiving keys.Step 2: Analyze options
const loader = new DataLoader(keys => batchFunction(keys)); correctly usesnew DataLoader(keys => batchFunction(keys)). Options B, C, and D use incorrect syntax or call batchFunction immediately.Final Answer:
const loader = new DataLoader(keys => batchFunction(keys)); -> Option CQuick Check:
Usenew DataLoader(batchFunction)[OK]
- Calling batchFunction immediately instead of passing it
- Using DataLoader as a function without 'new'
- Using a non-function argument
async function batchUsers(ids: readonly number[]) {
return ids.map(id => ({ id, name: `User${id}` }));
}What will be the output of
await loader.load(2) if loader is created with this batch function?Solution
Step 1: Understand batch function output
The batch function returns an array of user objects matching the input ids in order.Step 2: Understand loader.load behavior
loader.load(2)returns a single user object corresponding to id 2, not an array.Final Answer:
{ id: 2, name: 'User2' } -> Option AQuick Check:
load(id) returns single item from batch array [OK]
- Expecting load() to return an array
- Confusing batch function return with load return
- Assuming batch function returns undefined
async function batchPosts(ids: readonly number[]) {
const posts = await getPostsByIds(ids);
return posts;
}But you get wrong results when loading posts. What is the likely problem?
Solution
Step 1: Check batch function contract
DataLoader requires the batch function to return results in the same order as the keys array.Step 2: Analyze getPostsByIds behavior
IfgetPostsByIdsreturns posts unordered, the batch function breaks this contract causing wrong results.Final Answer:
The returned arraypostsis not in the same order asids-> Option AQuick Check:
Batch function must preserve order [OK]
- Ignoring order of returned array
- Thinking batch function can't be async
- Returning Map instead of array
Solution
Step 1: Understand DataLoader caching scope
DataLoader caches results per instance, so sharing one instance globally causes cross-request data leaks.Step 2: Use request-scoped provider
Creating a DataLoader instance per request via a request-scoped provider ensures isolated caches per user request.Final Answer:
Create a DataLoader instance inside a request-scoped provider injected into the resolver -> Option BQuick Check:
Request-scoped DataLoader avoids cross-request cache [OK]
- Using singleton DataLoader causing shared cache
- Creating DataLoader once in module constructor
- Using static instance shared globally
Ready to prove it?
Pass the test and this pattern moves unknown
