or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

index.mddocs/

DataLoader

DataLoader is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching.

Originally developed at Facebook, DataLoader solves the N+1 query problem by automatically batching individual data requests into efficient batch operations and caching results to prevent duplicate requests within the same application cycle.

Package Information

  • Package Name: dataloader
  • Package Type: npm
  • Language: JavaScript with Flow type annotations and TypeScript definitions
  • Installation: npm install dataloader

Core Imports

const DataLoader = require('dataloader');

For TypeScript/ES6:

import DataLoader from 'dataloader';
// Or for CommonJS export compatibility:
import DataLoader = require('dataloader');

Basic Usage

const DataLoader = require('dataloader');

// Create a batch loading function
async function batchGetUsers(userIds) {
  const users = await db.query('SELECT * FROM users WHERE id IN (?)', [userIds]);
  // Return results in same order as input keys
  return userIds.map(id => users.find(user => user.id === id) || new Error(`User ${id} not found`));
}

// Create a DataLoader instance
const userLoader = new DataLoader(batchGetUsers);

// Load individual users - these will be batched automatically
const user1 = await userLoader.load(1);
const user2 = await userLoader.load(2);

// Or load multiple users at once
const [user3, user4] = await userLoader.loadMany([3, 4]);

Architecture

DataLoader is built around several key concepts:

  • Batching: Automatically combines multiple individual load requests into batch requests within a single execution frame
  • Caching: Memoizes results to avoid duplicate requests within the same DataLoader instance
  • Request Coalescing: Multiple calls to load the same key return the same Promise instance
  • Error Handling: Individual errors in batch results are isolated and don't affect other keys in the batch
  • Lazy Execution: Batch requests are scheduled using the event loop to capture all requests in the current execution frame

Capabilities

DataLoader Constructor

Creates a new DataLoader instance with the specified batch loading function and optional configuration.

/**
 * Creates a new DataLoader instance
 * @param batchLoadFn - Function that loads multiple keys and returns Promise of results
 * @param options - Optional configuration object
 */
constructor<K, V, C = K>(
  batchLoadFn: BatchLoadFn<K, V>,
  options?: Options<K, V, C>
): DataLoader<K, V, C>;

type BatchLoadFn<K, V> = (keys: ReadonlyArray<K>) => Promise<ArrayLike<V | Error>>;

interface Options<K, V, C = K> {
  /** Enable/disable batching (default: true) */
  batch?: boolean;
  /** Maximum keys per batch (default: Infinity) */
  maxBatchSize?: number;
  /** Custom batch scheduling function */
  batchScheduleFn?: (callback: () => void) => void;
  /** Enable/disable caching (default: true) */
  cache?: boolean;
  /** Function to generate cache keys from load keys */
  cacheKeyFn?: (key: K) => C;
  /** Custom cache implementation (default: new Map()) */
  cacheMap?: CacheMap<C, Promise<V>> | null;
  /** Name for this DataLoader instance (useful for APM tools) */
  name?: string | null;
}

interface CacheMap<K, V> {
  get(key: K): V | void;
  set(key: K, value: V): any;
  delete(key: K): any;
  clear(): any;
}

Usage Examples:

// Basic usage
const userLoader = new DataLoader(async (userIds) => {
  const users = await getUsersByIds(userIds);
  return userIds.map(id => users[id] || new Error(`User ${id} not found`));
});

// With options
const userLoader = new DataLoader(batchLoadFn, {
  batch: true,
  maxBatchSize: 100,
  cache: true,
  cacheKeyFn: key => key.toString(),
  name: 'UserLoader'
});

// Disable batching (equivalent to maxBatchSize: 1)
const immediateLoader = new DataLoader(batchLoadFn, { batch: false });

// Custom cache
const userLoader = new DataLoader(batchLoadFn, {
  cacheMap: new Map() // or null to disable caching
});

Load Single Key

Loads a single key, returning a Promise for the associated value. Requests are automatically batched with other concurrent load requests.

/**
 * Loads a key, returning a Promise for the value represented by that key
 * @param key - The key to load
 * @returns Promise that resolves to the value for the given key
 * @throws TypeError if key is null or undefined
 */
load(key: K): Promise<V>;

Usage Examples:

// Single load
const user = await userLoader.load(123);

// Multiple concurrent loads (automatically batched)
const [user1, user2, user3] = await Promise.all([
  userLoader.load(1),
  userLoader.load(2),
  userLoader.load(3)
]);

// Error handling
try {
  const user = await userLoader.load(999);
} catch (error) {
  console.log('User not found:', error.message);
}

Load Multiple Keys

Loads multiple keys, promising an array of values or Error instances. Unlike Promise.all(), this method always resolves - individual errors become Error instances in the result array.

/**
 * Loads multiple keys, promising an array of values or Error instances
 * @param keys - Array-like object containing keys to load
 * @returns Promise that resolves to array of values or Error instances
 * @throws TypeError if keys is not array-like
 */
loadMany(keys: ArrayLike<K>): Promise<Array<V | Error>>;

Usage Examples:

// Load multiple users
const results = await userLoader.loadMany([1, 2, 3, 999]);
// results might be: [User{id:1}, User{id:2}, User{id:3}, Error('User 999 not found')]

// Handle mixed results
const [user1, user2, user3, error] = await userLoader.loadMany([1, 2, 3, 999]);
if (error instanceof Error) {
  console.log('Failed to load user 999:', error.message);
}

// Filter out errors
const users = results.filter(result => !(result instanceof Error));

Clear Cache Entry

Clears the value at a specific key from the cache, if it exists. Returns the DataLoader instance for method chaining.

/**
 * Clears the value at key from the cache, if it exists
 * @param key - The key to clear from cache
 * @returns This DataLoader instance for method chaining
 */
clear(key: K): this;

Usage Examples:

// Clear a specific user from cache
userLoader.clear(123);

// Method chaining
userLoader
  .clear(1)
  .clear(2)
  .clear(3);

// Clear after data mutation
async function updateUser(id, updates) {
  const user = await db.updateUser(id, updates);
  userLoader.clear(id); // Remove stale cached data
  return user;
}

Clear All Cache

Clears the entire cache. Useful when some event results in unknown invalidations across the DataLoader. Returns the DataLoader instance for method chaining.

/**
 * Clears the entire cache
 * @returns This DataLoader instance for method chaining
 */
clearAll(): this;

Usage Examples:

// Clear all cached data
userLoader.clearAll();

// Clear cache after bulk operations
async function bulkUpdateUsers(updates) {
  await db.bulkUpdate(updates);
  userLoader.clearAll(); // Invalidate all cached users
}

// Periodic cache clearing
setInterval(() => {
  userLoader.clearAll();
}, 300000); // Clear cache every 5 minutes

Prime Cache

Adds the provided key and value to the cache. If the key already exists, no change is made. Returns the DataLoader instance for method chaining.

/**
 * Adds the provided key and value to the cache
 * @param key - The key to prime
 * @param value - The value, Promise, or Error to associate with the key
 * @returns This DataLoader instance for method chaining
 */
prime(key: K, value: V | PromiseLike<V> | Error): this;

Usage Examples:

// Prime cache with known data
const newUser = await createUser({ name: 'Alice' });
userLoader.prime(newUser.id, newUser);

// Prime with error for known invalid keys
userLoader.prime(0, new Error('Invalid user ID'));

// Prime with promise
const userPromise = db.findUser(123);
userLoader.prime(123, userPromise);

// Method chaining
userLoader
  .prime(1, user1)
  .prime(2, user2)
  .prime(3, user3);

Instance Properties

Name Property

The name given to this DataLoader instance, useful for APM tools and debugging.

/**
 * The name given to this DataLoader instance
 * Useful for APM tools and debugging
 */
name: string | null;

Usage Examples:

// Set name in constructor
const userLoader = new DataLoader(batchLoadFn, { name: 'UserLoader' });
console.log(userLoader.name); // 'UserLoader'

// Access for debugging
function debugLoader(loader) {
  console.log(`DataLoader ${loader.name || 'unnamed'} cache status`);
}

Error Handling

DataLoader provides several error handling mechanisms:

Batch Function Errors

// Individual errors in batch results
async function batchLoadUsers(ids) {
  const users = await db.getUsers(ids);
  return ids.map(id => {
    const user = users.find(u => u.id === id);
    return user || new Error(`User ${id} not found`);
  });
}

// Synchronous errors in batch function
const loader = new DataLoader((keys) => {
  if (keys.length > 100) {
    throw new Error('Too many keys requested');
  }
  return batchLoad(keys);
});

Load Errors

try {
  const user = await userLoader.load(invalidId);
} catch (error) {
  // Handle individual load errors
  console.error('Failed to load user:', error);
}

// With loadMany, errors are returned as Error instances
const results = await userLoader.loadMany([1, 2, invalidId]);
results.forEach((result, index) => {
  if (result instanceof Error) {
    console.error(`Failed to load key ${keys[index]}:`, result.message);
  }
});

Common Patterns

Database Integration

// SQL with proper ordering
const userLoader = new DataLoader(async (userIds) => {
  const query = 'SELECT * FROM users WHERE id IN (?)';
  const users = await db.query(query, [userIds]);
  
  // Return results in same order as input keys
  return userIds.map(id => 
    users.find(user => user.id === id) || new Error(`User ${id} not found`)
  );
});

// Using with GraphQL resolvers
const resolvers = {
  Post: {
    author: (post) => userLoader.load(post.authorId),
  },
  Comment: {
    author: (comment) => userLoader.load(comment.authorId),
  }
};

Request-Scoped Loaders

// Express middleware
app.use((req, res, next) => {
  req.loaders = {
    user: new DataLoader(batchLoadUsers),
    post: new DataLoader(batchLoadPosts)
  };
  next();
});

// Use in routes
app.get('/posts/:id', async (req, res) => {
  const post = await req.loaders.post.load(req.params.id);
  const author = await req.loaders.user.load(post.authorId);
  res.json({ ...post, author });
});

Custom Cache Keys

// Case-insensitive user lookup
const userByEmailLoader = new DataLoader(
  async (emails) => {
    const users = await db.getUsersByEmail(emails);
    return emails.map(email => users[email.toLowerCase()] || null);
  },
  {
    cacheKeyFn: email => email.toLowerCase()
  }
);