DataLoader
A batching and caching layer that coalesces per-request data loads into a fraction of the backend calls.
Repository Health
Technical Analysis
DataLoader is a generic utility for an application’s data-fetching layer, providing a consistent API over any backend (SQL, key-value stores, REST or gRPC services) via automatic batching and per-request caching. Rather than replacing a database or cache, it sits in front of one: every .load(key) call within a single tick of the event loop is coalesced into a single call to a user-supplied batch function, and the resulting values are memoized for the lifetime of the DataLoader instance so repeated loads of the same key never hit the backend twice.
Originally a port of Facebook’s internal “Loader” abstraction that underpinned its early GraphQL server, DataLoader is the reference implementation the JavaScript GraphQL ecosystem builds on to avoid the N+1 query problem, and it has been reimplemented in a dozen other languages. It ships with zero runtime dependencies, a small five-method API surface (load, loadMany, clear, clearAll, prime), and enough configuration (custom cache maps, cache key functions, batch schedulers, max batch size) to fit almost any backend shape without changing how calling code is written.
What You Get
- Automatic request-scoped batching that coalesces all
.load()calls issued within a single tick of the event loop into one call to your batch function - A built-in memoization cache (backed by
Mapby default, or any custom cache-like object) that eliminates redundant loads for the same key within a request loadMany()for loading multiple keys at once, resolving per-key errors as values instead of rejecting the whole batchprime()to seed the cache ahead of time (e.g. cross-populate a by-ID loader from a by-username loader), andclear()/clearAll()for invalidating stale entries after a mutation- Pluggable
batchScheduleFn,cacheKeyFn, andcacheMapoptions for adapting the scheduling and caching strategy to non-default environments (browsers, long-lived processes, object keys) - Zero runtime dependencies and both Flow and hand-written TypeScript type definitions shipped in the package
Common Use Cases
- Resolving GraphQL fields without N+1 database queries, by giving each field resolver a shared per-request loader instead of querying the database directly
- Deduplicating and batching calls to a REST or gRPC backend service across unrelated parts of a request-handling pipeline
- Providing a per-request cache in front of SQL, Redis, CouchDB, RethinkDB, or Google Datastore lookups (patterns documented in the project’s examples directory)
- Loading the same entity by multiple keys (e.g. by ID and by username) while keeping both caches in sync via
prime() - Invalidating cached reads after a mutation within the same request, using
clear()/clearAll()to force a fresh load on subsequent calls
Under The Hood
Architecture The entire implementation lives in one ~500-line file (src/index.js) that exposes a single DataLoader class. Each instance tracks a current “batch” record (pending keys, resolve/reject callbacks, and cache-hit callbacks) that getCurrentBatch either reuses or creates; the first .load() call on a fresh batch schedules dispatch via a configurable batchScheduleFn, defaulting to enqueuePostPromiseJob, which chains a resolved Promise into process.nextTick (or setImmediate/setTimeout in non-Node environments) so the batch closes only after the current synchronous execution and any already-queued microtasks complete. dispatchBatch enforces the batch-function contract (must return a Promise resolving to an array-like of the same length as the input keys) and resolves or rejects each pending Promise by index, while resolveCacheHits replays cached results in the same tick so cached and freshly-loaded values settle together; failedDispatch centralizes cleanup for both synchronous throws and rejected batch promises, clearing the cache for every affected key and rejecting each callback. The Batch record is the one abstraction everything else depends on — changing its shape would ripple through getCurrentBatch, dispatchBatch, resolveCacheHits, and failedDispatch together.
Tech Stack Pure JavaScript with Flow (@flow strict) type annotations in the source, compiled through Babel (@babel/core, @babel/preset-env, @babel/preset-flow) into the published dist/ output, alongside a hand-maintained index.d.ts for TypeScript consumers. There are no runtime dependencies beyond the global ES6 Promise and Map. Releases are versioned and changelogged through Changesets and published to npm; CI (GitHub Actions) runs the full lint + flow-check + Jest suite across multiple Node versions via Yarn and reports coverage to Codecov.
Code Quality The test suite is extensive relative to the implementation size — well over a thousand lines across dedicated files for the core API, abusive/malformed-input scenarios, browser scheduling fallbacks, and unhandled-rejection behavior. Error handling is explicit throughout: constructor and option validators throw descriptive TypeErrors synchronously for misuse, and asynchronous batch failures are funneled through a single failedDispatch path so no request is left hanging. Naming is consistent (underscore-prefixed private fields, method names mirroring Promise.all semantics), and both a static type checker (Flow) and a separately maintained TypeScript declaration file back the public API, enforced together with ESLint and Prettier in CI.
API Design The public surface is deliberately small — five methods cover the whole batching and caching contract, and a DataLoader can be constructed with nothing more than a single batch function; every other behavior (batch size limits, custom scheduling, custom cache keys or cache backends) is opt-in with sensible defaults. Documentation is unusually thorough for a library this size, with a long-form README covering batching, caching, custom schedulers, custom cache implementations, and GraphQL integration, plus a dedicated examples directory walking through SQL, Redis, CouchDB, RethinkDB, Google Datastore, and Knex backends.
Used by 10 apps in this directory
GraphQL Hive
Developer Tools · Devops · Monitoring
Open-source GraphQL schema registry and observability platform with breaking change detection, federation support, and CI/CD integration for teams of any size.
KeystoneJS
CMS · Developer Tools
The superpowered headless CMS for developers built with GraphQL and React
Medplum
Developer Tools · Databases · Authentication
An open-source, FHIR-native healthcare platform that gives developers a compliant backend, authentication, a React component library, and serverless bots to build clinical applications in weeks instead of years.
NocoDB
No Code Platforms · Databases · Low Code Platforms
Turn any SQL database into a collaborative no-code spreadsheet with automatic REST APIs and real-time views.
Omnivore
Knowledge Management · Bookmarks Archiving · Note Taking
Self-hosted read-it-later platform with highlights, newsletters, PDFs, and seamless Obsidian and Logseq integration.
Payload CMS
Developer Tools · Blogging · CMS
The open-source, Next.js-native headless CMS that lives inside your /app folder and gives you a full TypeScript backend instantly.
Rocket.Chat
Team Chat
The secure, self-hosted team communications platform for organizations that cannot compromise on data sovereignty.
Sanity
CMS
Open-source headless CMS with a fully customizable React Studio, real-time collaborative editing, structured content modeling, and GROQ query language
twenty
CRM
The open-source CRM you build, ship, and version like the rest of your stack — with customizable objects, AI agents, and a TypeScript SDK.