Skill

Generate Production-Ready GraphQL Resolvers

Generate production-ready GraphQL resolvers with TypeScript, optimized for performance and error handling.

Works with apollojoi

91
Spark score
out of 100
Updated 5 months ago
Version 1.0.0
Models

Add to Favorites

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

01

Generate TypeScript resolvers following standard signatures.

02

Implement context management for dependency injection and request-specific data.

03

Optimize data fetching with DataLoader and query complexity analysis.

04

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.