Skip to main content

Quick Start

This guide will walk you through creating your first data loader with dldr. You’ll learn the core concepts and see real working examples.

Basic Usage

1

Import the load function

Start by importing load from dldr:
import { load } from 'dldr';
2

Create a loader function

Define a function that accepts an array of keys and returns a promise of results:
async function getPosts(keys: string[]) {
  // Fetch posts from your database
  return sql`SELECT id, name FROM posts WHERE id IN (${keys})`;
}
Your loader function must return a value (or Error) for each key in the same order.
3

Load your data

Use load() to fetch data. Multiple calls in the same tick are automatically batched:
const posts = await Promise.all([
  load(getPosts, '123'),
  load(getPosts, '123'), // Deduplicated!
  load(getPosts, '456'),
  load(getPosts, '789'),
]);
4

See the magic

Your loader is called just once with deduplicated keys:
// getPosts is called with: ['123', '456', '789']
// Returns:
// [
//   { id: '123', name: 'First Post' },
//   { id: '123', name: 'First Post' }, // Same result
//   { id: '456', name: 'Second Post' },
//   { id: '789', name: 'Third Post' },
// ]

Complete Example

Here’s a complete working example:
import { load } from 'dldr';

// Define your loader function
const getPosts = (keys: string[]) => 
  sql`SELECT id, name FROM posts WHERE id IN (${keys})`;

// For convenience, you can bind the loader
const loadPost = load.bind(null, getPosts);

// Build up your requests
const posts = [
  load(getPosts, '123'),
  loadPost('123'),      // Functionally equivalent to above
  load(getPosts, '456'),
];

// Add more requests
posts.push(load(getPosts, '789'));

// Batch and resolve all requests
const loaded = await Promise.all(posts);

// Your loader was called once with ['123', '456', '789']
console.log(loaded);
// [
//   { id: '123', name: '123' },
//   { id: '123', name: '123' },
//   { id: '456', name: '456' },
//   { id: '789', name: '789' },
// ]
Use .bind() to create a convenience function that doesn’t require passing the loader every time.

Using the Factory Helper

dldr provides a factory function for creating loaders with a stable reference:
import { factory } from 'dldr';

const loadPost = factory(async function (keys: string[]) {
  return keys.map(key => ({ id: key, name: `Post ${key}` }));
});

const values = await Promise.all([
  loadPost('bar'),
  loadPost('bar'),  // Deduplicated
  loadPost('baz'),
]);

console.log(values);
// [
//   { id: 'bar', name: 'Post bar' },
//   { id: 'bar', name: 'Post bar' },
//   { id: 'baz', name: 'Post baz' },
// ]

GraphQL Resolver Example

dldr is perfect for solving the N+1 problem in GraphQL:
import { load } from 'dldr';
import { buildSchema, graphql } from 'graphql';

const schema = buildSchema(`
  type Query {
    me(name: String!): String!
  }
`);

const operation = `{
  a: me(name: "John")
  b: me(name: "Jane")
}`;

const results = await graphql({
  schema,
  source: operation,
  contextValue: {
    getUser: load.bind(null, async (names) => {
      // Called once with ['John', 'Jane']
      const result = await fetchUsersFromDB(names);
      return result;
    }),
  },
  rootValue: {
    me: ({ name }, ctx) => {
      return ctx.getUser(name);
    },
  },
});
By binding the loader to the GraphQL context, all field resolutions in the same query are batched together.

How Batching Works

dldr uses queueMicrotask to batch operations:
1

Request collection

When you call load(), the request is added to a batch queue
2

Microtask scheduling

On the first call for a loader, a microtask is scheduled with queueMicrotask
3

Key deduplication

Duplicate keys are automatically deduplicated, but all callers receive the result
4

Batch execution

After the current tick completes, your loader is called once with all unique keys
5

Result distribution

Results are distributed back to each caller’s promise

Important Rules

Your loader function must return a value (or Error) for each key in the same order they were provided.
// ✅ Correct - returns value for each key
async function goodLoader(keys: string[]) {
  const results = await db.query('SELECT * FROM users WHERE id IN (?)', [keys]);
  return keys.map(key => 
    results.find(r => r.id === key) || new Error('Not found')
  );
}

// ❌ Wrong - might not return same length
async function badLoader(keys: string[]) {
  return db.query('SELECT * FROM users WHERE id IN (?)', [keys]);
  // Database might return fewer results than keys!
}

Next Steps

Caching

Learn how to cache results across ticks for even better performance

API Reference

Explore the complete API documentation

Build docs developers (and LLMs) love