Solving the N+1 Query Problem in GraphQL
GraphQL

Solving the N+1 Query Problem in GraphQL

O
Oussama Yahouni
January 8, 2026
10 min read
#GraphQL#Performance#Backend#Node.js#DataLoader

The N+1 query problem is one of the most common performance bottlenecks in GraphQL applications. Let me show you exactly what it is and how to fix it.

Understanding the Problem

Imagine you have this GraphQL query:

query { posts { id title author { id name } } }

With a naive implementation, here's what happens:

  1. One query to fetch all posts (10 posts)
  2. Ten queries to fetch the author for each post

Total: 11 queries for what should be 1-2 queries!

The Code That Causes N+1

const resolvers = { Query: { posts: () => db.posts.findAll(), }, Post: { // ⚠️ This runs once PER post! author: (post) => db.users.findById(post.authorId), }, };

For 10 posts, author resolver runs 10 times = 10 separate database queries!

The Solution: DataLoader

DataLoader batches and caches requests within a single request cycle.

Installation

npm install dataloader

Basic Implementation

import DataLoader from 'dataloader'; // Batch function: receives array of IDs, returns array of Users const batchUsers = async (ids) => { const users = await db.users.findAll({ where: { id: ids } }); // IMPORTANT: Return users in same order as IDs! return ids.map(id => users.find(user => user.id === id) ); }; // Create DataLoader const userLoader = new DataLoader(batchUsers); // Use in resolvers const resolvers = { Post: { author: (post) => userLoader.load(post.authorId), }, };

Real-World Example

Here's a complete setup for a blog application:

// loaders.js import DataLoader from 'dataloader'; import { User, Post, Comment } from './models'; export function createLoaders() { // User loader const userLoader = new DataLoader(async (ids) => { const users = await User.findAll({ where: { id: ids } }); return ids.map(id => users.find(u => u.id === id) || null); }); // Posts by user loader const postsByUserLoader = new DataLoader(async (userIds) => { const posts = await Post.findAll({ where: { authorId: userIds } }); return userIds.map(userId => posts.filter(post => post.authorId === userId) ); }); return { users: userLoader, postsByUser: postsByUserLoader, }; }

Conclusion

DataLoader is essential for production GraphQL APIs. Key takeaways:

  1. Always use DataLoader for related data fetching
  2. Create per-request - New loaders for each request
  3. Maintain order - Return results in same order as keys
  4. Handle nulls - Return null for missing items
  5. Monitor - Log batch sizes to verify it's working

This pattern has helped me reduce database queries by 90%+ in production GraphQL APIs.

Happy optimizing! 🚀

Found this helpful?

Share it with your network!

Back to All Articles