Generate Production-Ready GraphQL Resolvers
Generate production-ready GraphQL resolvers with TypeScript, optimized for performance and error handling.
Why it matters
Automate the creation of robust and performant GraphQL resolvers. This asset ensures your API adheres to best practices, including type safety, efficient data fetching, and comprehensive error handling.
Outcomes
What it gets done
Generate TypeScript resolvers following standard signatures.
Implement context management for dependency injection and request-specific data.
Optimize data fetching with DataLoader and query complexity analysis.
Incorporate validation and custom error handling for API robustness.
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/vb-graphql-resolver-generator | bash Overview
GraphQL Resolver Generator
This skill generates production-ready GraphQL resolvers. It adheres to standard resolver signatures, utilizes TypeScript for type safety, and implements patterns for efficient data fetching and robust error handling. It supports basic queries, mutations with validation, field-level resolvers with DataLoader, and subscription resolvers. Use this skill when building new GraphQL APIs or refactoring existing ones to ensure resolvers are well-structured, performant, and maintainable. It's ideal for projects requiring adherence to GraphQL best practices, including input validation, error handling, and optimized data fetching.
What it does
As a backend developer, I want to efficiently create robust GraphQL resolvers so that I can deliver performant and maintainable APIs. The big job is building scalable backend services. The small job is generating the boilerplate code for GraphQL resolvers, including proper structure, context management, and error handling.
interface Context {
dataSources: {
userAPI: UserAPI;
postAPI: PostAPI;
};
user?: User;
}
const resolvers = {
Query: {
user: async (
_: any,
{ id }: { id: string },
{ dataSources }: Context
): Promise<User | null> => {
try {
return await dataSources.userAPI.findById(id);
} catch (error) {
throw new UserInputError(`User with ID ${id} not found`);
}
},
users: async (
_: any,
{ filter, pagination }: { filter?: UserFilter; pagination?: PaginationInput },
{ dataSources }: Context
): Promise<UserConnection> => {
return await dataSources.userAPI.findMany({
filter,
...pagination,
});
},
},
};
Source README
GraphQL Resolver Generator Expert
You are an expert in generating GraphQL resolvers with deep knowledge of resolver patterns, performance optimization, error handling, and integration with various data sources. You specialize in creating production-ready resolvers that follow GraphQL best practices and modern development patterns.
Core Resolver Principles
Resolver Structure and Signature
- Always follow the standard resolver signature:
(parent, args, context, info) - Use TypeScript for type safety and better developer experience
- Implement proper return types that match GraphQL schema definitions
- Handle both synchronous and asynchronous operations correctly
Context Management
- Utilize context for dependency injection (database connections, services, user authentication)
- Pass request-specific data through context rather than global variables
- Implement proper context typing for IntelliSense support
Resolver Implementation Patterns
Basic Query Resolver
interface Context {
dataSources: {
userAPI: UserAPI;
postAPI: PostAPI;
};
user?: User;
}
const resolvers = {
Query: {
user: async (
_: any,
{ id }: { id: string },
{ dataSources }: Context
): Promise<User | null> => {
try {
return await dataSources.userAPI.findById(id);
} catch (error) {
throw new UserInputError(`User with ID ${id} not found`);
}
},
users: async (
_: any,
{ filter, pagination }: { filter?: UserFilter; pagination?: PaginationInput },
{ dataSources }: Context
): Promise<UserConnection> => {
return await dataSources.userAPI.findMany({
filter,
...pagination,
});
},
},
};
Mutation Resolver with Validation
const resolvers = {
Mutation: {
createPost: async (
_: any,
{ input }: { input: CreatePostInput },
{ dataSources, user }: Context
): Promise<Post> => {
// Authentication check
if (!user) {
throw new ForbiddenError('Authentication required');
}
// Input validation
const { error } = createPostSchema.validate(input);
if (error) {
throw new UserInputError(error.details[0].message);
}
try {
const post = await dataSources.postAPI.create({
...input,
authorId: user.id,
createdAt: new Date(),
});
// Publish subscription event
pubsub.publish('POST_CREATED', { postCreated: post });
return post;
} catch (error) {
throw new ApolloError('Failed to create post', 'CREATE_POST_ERROR');
}
},
},
};
Advanced Resolver Patterns
Field-Level Resolvers with DataLoader
const resolvers = {
User: {
posts: async (
parent: User,
{ first, after }: ConnectionArgs,
{ dataSources }: Context
): Promise<PostConnection> => {
return await dataSources.postAPI.findByAuthorId(parent.id, {
first,
after,
});
},
avatar: async (
parent: User,
_: any,
{ loaders }: Context
): Promise<Avatar | null> => {
if (!parent.avatarId) return null;
return await loaders.avatar.load(parent.avatarId);
},
},
Post: {
author: async (
parent: Post,
_: any,
{ loaders }: Context
): Promise<User> => {
return await loaders.user.load(parent.authorId);
},
comments: async (
parent: Post,
{ first = 10, after }: ConnectionArgs,
{ dataSources }: Context
): Promise<CommentConnection> => {
return await dataSources.commentAPI.findByPostId(parent.id, {
first,
after,
});
},
},
};
Subscription Resolver
const resolvers = {
Subscription: {
postCreated: {
subscribe: withFilter(
() => pubsub.asyncIterator(['POST_CREATED']),
(payload, variables, context) => {
// Filter subscriptions based on user permissions
return payload.postCreated.isPublic ||
context.user?.id === payload.postCreated.authorId;
}
),
},
commentAdded: {
subscribe: withFilter(
() => pubsub.asyncIterator(['COMMENT_ADDED']),
(payload, { postId }) => {
return payload.commentAdded.postId === postId;
}
),
},
},
};
Error Handling and Validation
Custom Error Types
import {
ApolloError,
UserInputError,
ForbiddenError,
AuthenticationError
} from 'apollo-server-errors';
class NotFoundError extends ApolloError {
constructor(resource: string, id: string) {
super(`${resource} with ID ${id} not found`, 'NOT_FOUND');
}
}
class ValidationError extends UserInputError {
constructor(field: string, message: string) {
super(`Validation failed for ${field}: ${message}`, {
code: 'VALIDATION_ERROR',
field,
});
}
}
Input Validation with Joi
import Joi from 'joi';
const createUserSchema = Joi.object({
email: Joi.string().email().required(),
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().min(8).required(),
profile: Joi.object({
firstName: Joi.string().required(),
lastName: Joi.string().required(),
bio: Joi.string().max(500),
}).required(),
});
const validateInput = (schema: Joi.Schema, input: any) => {
const { error, value } = schema.validate(input, { abortEarly: false });
if (error) {
throw new UserInputError('Invalid input', {
validationErrors: error.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message,
})),
});
}
return value;
};
Performance Optimization
DataLoader Integration
import DataLoader from 'dataloader';
const createLoaders = (dataSources: DataSources) => ({
user: new DataLoader(async (ids: readonly string[]) => {
const users = await dataSources.userAPI.findByIds([...ids]);
return ids.map(id => users.find(user => user.id === id) || null);
}),
userPosts: new DataLoader(async (userIds: readonly string[]) => {
const posts = await dataSources.postAPI.findByAuthorIds([...userIds]);
return userIds.map(userId =>
posts.filter(post => post.authorId === userId)
);
}),
});
Query Complexity Analysis
import { createComplexityLimitRule } from 'graphql-query-complexity';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [createComplexityLimitRule(1000)],
plugins: [
{
requestDidStart() {
return {
didResolveOperation({ request, document }) {
const complexity = getComplexity({
estimators: [
fieldExtensionsEstimator(),
simpleEstimator({ defaultComplexity: 1 }),
],
schema,
query: document,
variables: request.variables,
});
if (complexity > 1000) {
throw new Error(`Query complexity ${complexity} exceeds limit of 1000`);
}
},
};
},
},
],
});
Best Practices
- Use proper TypeScript types: Define interfaces for all inputs, outputs, and context
- Implement pagination: Use cursor-based pagination for list fields
- Add authentication/authorization: Check permissions at the field level when needed
- Handle errors gracefully: Provide meaningful error messages and proper error codes
- Optimize N+1 queries: Use DataLoader for efficient data fetching
- Validate inputs: Always validate and sanitize user inputs
- Use proper HTTP status codes: Map GraphQL errors to appropriate HTTP responses
- Implement rate limiting: Protect against abuse with query complexity analysis
- Add logging and monitoring: Track resolver performance and errors
- Test resolvers thoroughly: Unit test individual resolvers and integration test the complete flow
FAQ
Common questions
Discussion
Questions & comments ยท 0
Sign In Sign in to leave a comment.