
graphql-resolvers
Write efficient resolvers with DataLoader, batching, and N+1 prevention
1étoiles
0forks
Mis à jour 1/5/2026
SKILL.md
readonlyread-only
name
graphql-resolvers
description
Write efficient resolvers with DataLoader, batching, and N+1 prevention
version
"2.0.0"
GraphQL Resolvers Skill
Build performant data fetching with proper patterns
Overview
Master resolver implementation including the critical DataLoader pattern for preventing N+1 queries, context design, and error handling strategies.
Quick Reference
| Pattern | Purpose | When to Use |
|---|---|---|
| DataLoader | Batch + cache | Any relationship field |
| Context | Request-scoped data | Auth, loaders, datasources |
| Field resolver | Computed fields | Derived data |
| Root resolver | Entry points | Query/Mutation fields |
Core Patterns
1. Resolver Signature
// (parent, args, context, info) => result
const resolvers = {
Query: {
// Root resolver - parent is undefined
user: async (_, { id }, { dataSources }) => {
return dataSources.users.findById(id);
},
},
User: {
// Field resolver - parent is User object
posts: async (user, { first = 10 }, { loaders }) => {
return loaders.postsByAuthor.load(user.id);
},
// Computed field - sync is fine
fullName: (user) => `${user.firstName} ${user.lastName}`,
// Default resolver (implicit)
// email: (user) => user.email,
},
};
2. DataLoader Pattern
const DataLoader = require('dataloader');
// N+1 Problem:
// Query: { users { posts { title } } }
// Without DataLoader: 1 + N queries
// Solution: Batch loading
const createLoaders = () => ({
// Batch by foreign key
postsByAuthor: new DataLoader(async (authorIds) => {
// 1. Single query for all authors
const posts = await db.posts.findAll({
where: { authorId: { [Op.in]: authorIds } }
});
// 2. Group by author
const postsByAuthor = {};
posts.forEach(post => {
if (!postsByAuthor[post.authorId]) {
postsByAuthor[post.authorId] = [];
}
postsByAuthor[post.authorId].push(post);
});
// 3. Return in same order as input
return authorIds.map(id => postsByAuthor[id] || []);
}),
// Batch by primary key
userById: new DataLoader(async (ids) => {
const users = await db.users.findAll({
where: { id: { [Op.in]: ids } }
});
const userMap = new Map(users.map(u => [u.id, u]));
return ids.map(id => userMap.get(id) || null);
}),
});
// Usage in resolvers
const resolvers = {
Post: {
author: (post, _, { loaders }) => {
return loaders.userById.load(post.authorId);
},
},
User: {
posts: (user, _, { loaders }) => {
return loaders.postsByAuthor.load(user.id);
},
},
};
3. Context Setup
const createContext = async ({ req, res }) => {
// 1. Parse auth token
const token = req.headers.authorization?.replace('Bearer ', '');
const user = token ? await verifyToken(token) : null;
// 2. Create request-scoped loaders (IMPORTANT!)
const loaders = createLoaders();
// 3. Initialize data sources
const dataSources = {
users: new UserDataSource(db),
posts: new PostDataSource(db),
};
// 4. Request metadata
const requestId = req.headers['x-request-id'] || crypto.randomUUID();
return {
user,
loaders,
dataSources,
requestId,
};
};
// Apollo Server setup
const server = new ApolloServer({
typeDefs,
resolvers,
context: createContext,
});
4. Error Handling
import { GraphQLError } from 'graphql';
const resolvers = {
Mutation: {
createUser: async (_, { input }, { dataSources, user }) => {
// 1. Auth check
if (!user) {
throw new GraphQLError('Not authenticated', {
extensions: { code: 'UNAUTHENTICATED' }
});
}
// 2. Validation (return errors, don't throw)
const validationErrors = validateInput(input);
if (validationErrors.length > 0) {
return { user: null, errors: validationErrors };
}
// 3. Business logic
try {
const newUser = await dataSources.users.create(input);
return { user: newUser, errors: [] };
} catch (error) {
// Known error
if (error.code === 'DUPLICATE_EMAIL') {
return {
user: null,
errors: [{ field: 'email', message: 'Already exists' }]
};
}
// Unknown error - throw
throw new GraphQLError('Internal error', {
extensions: { code: 'INTERNAL_ERROR' }
});
}
},
},
};
5. Subscription Resolvers
import { PubSub, withFilter } from 'graphql-subscriptions';
const pubsub = new PubSub();
const resolvers = {
Mutation: {
sendMessage: async (_, { input }, { dataSources }) => {
const message = await dataSources.messages.create(input);
// Publish event
pubsub.publish('MESSAGE_SENT', {
messageSent: message,
channelId: input.channelId,
});
return message;
},
},
Subscription: {
// Simple subscription
userCreated: {
subscribe: () => pubsub.asyncIterator(['USER_CREATED']),
},
// Filtered subscription
messageSent: {
subscribe: withFilter(
() => pubsub.asyncIterator(['MESSAGE_SENT']),
(payload, variables) => {
return payload.channelId === variables.channelId;
}
),
},
},
};
Performance Targets
| Resolver Type | Target | Action if Exceeded |
|---|---|---|
| Simple field | < 10ms | Check DB indexes |
| DataLoader batch | < 50ms | Optimize query |
| Complex computation | < 200ms | Consider caching |
| Total request | < 500ms | Profile and optimize |
Troubleshooting
| Issue | Symptom | Solution |
|---|---|---|
| N+1 queries | Slow, many DB calls | Use DataLoader |
| Memory leak | Growing memory | Create loaders per request |
| Stale data | Wrong results | Clear DataLoader cache |
| Race condition | Intermittent errors | Don't mutate context |
Debug Techniques
// 1. Log DataLoader batches
const loader = new DataLoader(async (keys) => {
console.log(`Batching ${keys.length} keys`);
// ...
});
// 2. Time resolvers
const withTiming = (resolver) => async (...args) => {
const start = Date.now();
const result = await resolver(...args);
console.log(`Took ${Date.now() - start}ms`);
return result;
};
// 3. Request logging plugin
const loggingPlugin = {
requestDidStart() {
const start = Date.now();
return {
willSendResponse() {
console.log(`Request took ${Date.now() - start}ms`);
},
};
},
};
Usage
Skill("graphql-resolvers")
Related Skills
graphql-schema-design- Schema that resolvers implementgraphql-apollo-server- Server configurationgraphql-security- Auth in resolvers
Related Agent
03-graphql-resolvers- For detailed guidance
You Might Also Like
Related Skills

gog
169Kdev-api
Google Workspace CLI for Gmail, Calendar, Drive, Contacts, Sheets, and Docs.
openclaw
orpc-contract-first
127Kdev-api
Guide for implementing oRPC contract-first API patterns in Dify frontend. Triggers when creating new API contracts, adding service endpoints, integrating TanStack Query with typed contracts, or migrating legacy service calls to oRPC. Use for all API layer work in web/contract and web/service directories.
langgenius
