DataLoader

2.2.3 · active · verified Tue Apr 21

DataLoader is a JavaScript utility designed to optimize data fetching from various backend sources like databases or web services. It achieves this by intelligently batching multiple individual data requests into a single operation and caching results, significantly reducing the number of round-trips to the backend. The current stable version is 2.2.3, with minor patch releases occurring regularly to address fixes and small improvements. Major versions, like v2.0.0, introduce breaking changes and significant architectural updates, such as becoming part of the GraphQL Foundation. A key differentiator is its core focus on solving the N+1 problem by providing a simple, consistent API that coalesces requests within a single event loop tick, making it particularly valuable in GraphQL server implementations where diverse data requirements are common. It ships with TypeScript types, ensuring robust development in typed environments and is generally released on an as-needed basis rather than a fixed cadence.

Common errors

Warnings

Install

Imports

Quickstart

This example demonstrates creating a DataLoader instance, performing batched requests for multiple user IDs within a single event loop tick, chaining requests, and retrieving cached values, showing how it minimizes backend calls.

import { DataLoader } from 'dataloader';

interface User { id: number; name: string; invitedByID?: number; }

// Simulate a backend API call that fetches users by a batch of IDs
async function myBatchGetUsers(ids: readonly number[]): Promise<(User | Error)[]> {
  console.log(`[DB] Fetching users with IDs: ${ids.join(', ')}`);
  // In a real app, this would be a single database query or API call
  const users: User[] = ids.map(id => ({
    id,
    name: `User ${id}`,
    invitedByID: id > 1 ? id - 1 : undefined // Example for chaining loads
  }));
  // Return in the same order as requested keys
  return ids.map(id => users.find(u => u.id === id) || new Error(`User ${id} not found`));
}

async function main() {
  // Create a new DataLoader instance per request (or execution context)
  const userLoader = new DataLoader<number, User>(myBatchGetUsers, {
    cache: true, // Caching is enabled by default, explicitly shown for clarity
    cacheKeyFn: (key) => `user:${key}` // Custom cache key for clarity, default works for primitives
  });

  console.log('--- Initiating parallel loads (will be batched) ---');
  const [user1, user2] = await Promise.all([
    userLoader.load(1),
    userLoader.load(2)
  ]);

  if (user1 instanceof Error || user2 instanceof Error) {
    console.error('Error loading users:', user1, user2);
    return;
  }
  console.log(`Loaded User 1: ${user1.name}`);
  console.log(`Loaded User 2: ${user2.name}`);

  console.log('\n--- Chaining loads (will be batched if in same tick) ---');
  const invitedBy1 = await userLoader.load(user1.invitedByID || 0); // Assuming user1 has invitedByID
  const invitedBy2 = await userLoader.load(user2.invitedByID || 0); // Assuming user2 has invitedByID

  if (invitedBy1 instanceof Error || invitedBy2 instanceof Error) {
    console.error('Error loading invitedBy users:', invitedBy1, invitedBy2);
    return;
  }
  console.log(`User ${user1.id} was invited by ${invitedBy1.name}`);
  console.log(`User ${user2.id} was invited by ${invitedBy2.name}`);

  console.log('\n--- Loading cached value (no DB call) ---');
  const cachedUser1 = await userLoader.load(1);
  console.log(`Loaded cached User 1: ${cachedUser1 instanceof Error ? 'Error' : cachedUser1.name}`);
}

main().catch(console.error);

view raw JSON →