SyntaxStudy
Sign Up
GraphQL The N+1 Problem and DataLoader
GraphQL Beginner 1 min read

The N+1 Problem and DataLoader

The N+1 problem occurs when resolving a list of N items each triggers a separate database query for a related resource. For example, fetching 100 posts where each post resolver separately queries its author results in 101 queries instead of 2. Facebook's DataLoader library solves this with batching and caching. DataLoader collects all keys requested within a single event loop tick, then calls your batch function once with all keys. It also memoizes results within a request, so the same user is never fetched twice even if referenced from multiple posts.
Example
// npm install dataloader

import DataLoader from 'dataloader';

// Batch function: receives array of IDs, returns array of users in same order
async function batchLoadUsers(ids) {
  const users = await db.users.findAll({ where: { id: ids } });
  // Must return results in the SAME ORDER as ids
  const userMap = Object.fromEntries(users.map(u => [u.id, u]));
  return ids.map(id => userMap[id] ?? new Error(`User ${id} not found`));
}

// Create a DataLoader per request (never share across requests!)
function createLoaders() {
  return {
    user: new DataLoader(batchLoadUsers),
    // Add more loaders as needed
    postsByUser: new DataLoader(async (userIds) => {
      const posts = await db.posts.findAll({ where: { authorId: userIds } });
      return userIds.map(uid => posts.filter(p => p.authorId === uid));
    }),
  };
}

// Pass loaders via context in Apollo Server
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => ({
    currentUser: authenticate(req),
    loaders: createLoaders(),   // fresh loaders per request
    db,
  }),
});

// In resolver — DataLoader batches automatically
const resolvers = {
  Post: {
    author: (post, _, { loaders }) => loaders.user.load(post.authorId),
    // 100 posts → 1 DB query instead of 100
  },
};