graphql-resolvers

Write efficient resolvers with DataLoader, batching, and N+1 prevention

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "graphql-resolvers" with this command: npx skills add pluginagentmarketplace/custom-plugin-graphql/pluginagentmarketplace-custom-plugin-graphql-graphql-resolvers

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

PatternPurposeWhen to Use
DataLoaderBatch + cacheAny relationship field
ContextRequest-scoped dataAuth, loaders, datasources
Field resolverComputed fieldsDerived data
Root resolverEntry pointsQuery/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 TypeTargetAction if Exceeded
Simple field< 10msCheck DB indexes
DataLoader batch< 50msOptimize query
Complex computation< 200msConsider caching
Total request< 500msProfile and optimize

Troubleshooting

IssueSymptomSolution
N+1 queriesSlow, many DB callsUse DataLoader
Memory leakGrowing memoryCreate loaders per request
Stale dataWrong resultsClear DataLoader cache
Race conditionIntermittent errorsDon'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 implement
  • graphql-apollo-server - Server configuration
  • graphql-security - Auth in resolvers

Related Agent

  • 03-graphql-resolvers - For detailed guidance

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

graphql-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

graphql-apollo-server

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

graphql-schema-design

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

graphql-codegen

No summary provided by upstream source.

Repository SourceNeeds Review