{"id":15094,"library":"dataloader","title":"DataLoader","description":"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.","status":"active","version":"2.2.3","language":"javascript","source_language":"en","source_url":"ssh://git@github.com/graphql/dataloader","tags":["javascript","typescript"],"install":[{"cmd":"npm install dataloader","lang":"bash","label":"npm"},{"cmd":"yarn add dataloader","lang":"bash","label":"yarn"},{"cmd":"pnpm add dataloader","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"Preferred ESM import for modern Node.js and bundlers. The CommonJS `require` pattern is also valid but less idiomatic for newer projects.","wrong":"const DataLoader = require('dataloader');","symbol":"DataLoader","correct":"import { DataLoader } from 'dataloader';"},{"note":"Type import for the batch loading function signature, useful for TypeScript development.","symbol":"BatchLoadFn","correct":"import type { BatchLoadFn } from 'dataloader';"},{"note":"CommonJS import for Node.js environments that do not support or are not configured for ESM. This will correctly import the CJS export.","symbol":"DataLoader","correct":"const DataLoader = require('dataloader');"}],"quickstart":{"code":"import { DataLoader } from 'dataloader';\n\ninterface User { id: number; name: string; invitedByID?: number; }\n\n// Simulate a backend API call that fetches users by a batch of IDs\nasync function myBatchGetUsers(ids: readonly number[]): Promise<(User | Error)[]> {\n  console.log(`[DB] Fetching users with IDs: ${ids.join(', ')}`);\n  // In a real app, this would be a single database query or API call\n  const users: User[] = ids.map(id => ({\n    id,\n    name: `User ${id}`,\n    invitedByID: id > 1 ? id - 1 : undefined // Example for chaining loads\n  }));\n  // Return in the same order as requested keys\n  return ids.map(id => users.find(u => u.id === id) || new Error(`User ${id} not found`));\n}\n\nasync function main() {\n  // Create a new DataLoader instance per request (or execution context)\n  const userLoader = new DataLoader<number, User>(myBatchGetUsers, {\n    cache: true, // Caching is enabled by default, explicitly shown for clarity\n    cacheKeyFn: (key) => `user:${key}` // Custom cache key for clarity, default works for primitives\n  });\n\n  console.log('--- Initiating parallel loads (will be batched) ---');\n  const [user1, user2] = await Promise.all([\n    userLoader.load(1),\n    userLoader.load(2)\n  ]);\n\n  if (user1 instanceof Error || user2 instanceof Error) {\n    console.error('Error loading users:', user1, user2);\n    return;\n  }\n  console.log(`Loaded User 1: ${user1.name}`);\n  console.log(`Loaded User 2: ${user2.name}`);\n\n  console.log('\\n--- Chaining loads (will be batched if in same tick) ---');\n  const invitedBy1 = await userLoader.load(user1.invitedByID || 0); // Assuming user1 has invitedByID\n  const invitedBy2 = await userLoader.load(user2.invitedByID || 0); // Assuming user2 has invitedByID\n\n  if (invitedBy1 instanceof Error || invitedBy2 instanceof Error) {\n    console.error('Error loading invitedBy users:', invitedBy1, invitedBy2);\n    return;\n  }\n  console.log(`User ${user1.id} was invited by ${invitedBy1.name}`);\n  console.log(`User ${user2.id} was invited by ${invitedBy2.name}`);\n\n  console.log('\\n--- Loading cached value (no DB call) ---');\n  const cachedUser1 = await userLoader.load(1);\n  console.log(`Loaded cached User 1: ${cachedUser1 instanceof Error ? 'Error' : cachedUser1.name}`);\n}\n\nmain().catch(console.error);\n","lang":"typescript","description":"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."},"warnings":[{"fix":"Update `.loadMany()` result handling: `const results = await loader.loadMany([1,2,3]); results.forEach(res => { if (res instanceof Error) { /* handle error */ } else { /* handle value */ } });`","message":"As of v2.0.0, the `.loadMany()` method now returns an Array which may contain `Error` objects for individual keys that failed, rather than throwing a single error for the entire batch. Consumers must now explicitly check for `Error` instances within the returned array.","severity":"breaking","affected_versions":">=2.0.0"},{"fix":"Instantiate `new DataLoader(...)` within the scope of each incoming request (e.g., inside a middleware or request handler function).","message":"DataLoader instances typically represent a unique cache and context. In web servers (e.g., Express, GraphQL), a new DataLoader instance should be created per-request to prevent data leaks between different users and ensure appropriate caching semantics for mutable data. Do not use a single global DataLoader instance across all requests.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Instead of `const a = await loader.load(keyA); const b = await loader.load(keyB);`, use `const [a, b] = await Promise.all([loader.load(keyA), loader.load(keyB)]);` to ensure concurrent loads are batched.","message":"DataLoader's batching mechanism coalesces individual `load()` calls that occur within a single 'tick' of the JavaScript event loop. If you `await` a `load()` call before making another, the second `load()` call will not be batched with the first, potentially leading to N+1 queries. Structure your code to defer `await` until all desired `load()` calls have been queued, often by using `Promise.all()`.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Ensure your batch loading function maps each input key to its corresponding result or an Error object, preserving the original order and array length. Example: `keys.map(key => myMap.get(key) || new Error('NotFound'))`.","message":"The batch loading function provided to DataLoader *must* return a Promise that resolves to an Array of values (or Errors) that is exactly the same length as the input `keys` Array, and in the *same order* as the input keys. Failing to adhere to this contract will result in an `Invariant Violation` error.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Initialize DataLoader with `{ cacheKeyFn: key => JSON.stringify(key) }` or a function that produces a unique, serializable string from your object keys.","message":"When using complex objects as keys for `loader.load()`, JavaScript's default Map behavior uses reference equality. If you pass different object instances with the same logical content, they will be treated as distinct keys, bypassing the cache. To address this, provide a custom `cacheKeyFn`.","severity":"gotcha","affected_versions":">=1.1.0"},{"fix":"Be mindful of performance expectations when using DataLoader in a browser environment, as its batching might not be as efficient as in Node.js due to differences in event loop scheduling. Test thoroughly in target browser environments.","message":"While DataLoader gained direct browser support in v1.4.0, it explicitly notes that it \"cannot rely on the same post-promise job queuing behavior that allows for best performance in Node environments.\" This implies a fallback behavior is used, and performance characteristics in the browser might differ.","severity":"deprecated","affected_versions":">=1.4.0"}],"env_vars":null,"last_verified":"2026-04-21T00:00:00.000Z","next_check":"2026-07-20T00:00:00.000Z","problems":[{"fix":"Use the correct named import for ESM: `import { DataLoader } from 'dataloader';`. For CommonJS, use `const DataLoader = require('dataloader');` to ensure you're getting the constructor.","cause":"Attempting to use `new DataLoader()` after importing with `import DataLoader from 'dataloader';` (a default import) when `dataloader` primarily exports a named `DataLoader` class in ESM, or an incorrect `require` statement.","error":"TypeError: DataLoader is not a constructor"},{"fix":"Review your batch loading function to ensure it always returns a Promise that resolves to an array whose length is exactly equal to the `keys` array it received, and that each element corresponds to the respective key in order (either a value or an `Error` object).","cause":"The batch loading function passed to the `DataLoader` constructor returned an array with a different number of elements than the input `keys` array, or did not return a Promise resolving to such an array.","error":"Invariant Violation: DataLoader must be constructed with a function which accepts an Array of keys and returns a Promise which resolves to an Array of the same length as the Array of keys."}],"ecosystem":"npm"}