Data loaders
GraphQL resolvers often load the same entity multiple times during a single request. A DataLoader batches those loads into one call and caches the result within the request.
How it works
Section titled “How it works”AppModulecreates a new set of DataLoaders for every GraphQL request.- The loaders are passed in the context.
- Resolvers call
load(id)instead of querying the database directly. - DataLoader collects all
loadcalls in the same event loop tick and resolves them in a single batch. - The result is cached for the rest of the request.
UserDataLoader
Section titled “UserDataLoader”The starter provides a UserDataLoader factory:
@Injectable()export class UserDataLoader { constructor(private readonly userService: UserService) {}
createLoader(): DataLoader<string, User | null> { return new DataLoader<string, User | null>( async (userIds: readonly string[]) => { const users = await Promise.all( userIds.map((id) => this.userService.findById(id)), ); return users; }, { cache: true, batch: true }, ); }}The loader returns users in the same order as the requested IDs.
Using a loader in a resolver
Section titled “Using a loader in a resolver”@Resolver(() => UserType)export class UserResolver { constructor(private readonly userService: UserService) {}
@Query(() => UserType) async currentUser( @Context() ctx: { loaders: IDataLoaders }, @CurrentUser() user: User, ) { return ctx.loaders.userLoader.load(user.id); }}Why per-request instances
Section titled “Why per-request instances”A DataLoader instance must not be shared across requests. Its cache is scoped to a single request; otherwise, stale data would leak between users. The GraphQL context creates a new loader for every request.
Adding a new loader
Section titled “Adding a new loader”- Create a new loader class with a
createLoadermethod. - Add it to the
IDataLoadersinterface. - Add the loader factory to
CommonModuleexports. - Create the loader in the GraphQL context in
AppModule. - Inject
ctx.loaders.<loaderName>in your resolver.
Current limitation
Section titled “Current limitation”The provided UserDataLoader resolves one findById call per ID. When you switch to a real database, replace the Promise.all(...findById) with a single findByIds query to get the full benefit of batching. The loader structure stays the same; only the data access changes.