All skills
apollographql avatar

/apollo-server

@1e1ba0e official
by Apollo GraphQLapollographql/skills115 stars
13

Guide for building GraphQL servers with Apollo Server 5.x. Use this skill when: (1) setting up a new Apollo Server project, (2) writing resolvers or defining GraphQL schemas, (3) implementing authentication or authorization, (4) creating plugins or custom data sources, (5) troubleshooting Apollo Server errors or performance issues.

Use this Skill: https://skilld.dev/gh/apollographql/skills/apollo-server

This session only. Nothing lands on disk.

referencesresolvers.md

≈2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Resolvers Reference

Table of Contents

Resolver Signature

Every resolver receives four positional arguments:

(parent, args, contextValue, info) => result;

Arguments

Argument Description
parent Return value of the parent resolver. For root types (Query, Mutation), this is undefined.
args Object containing all arguments passed to the field.
contextValue Shared object for all resolvers in a request. Contains auth, dataSources, etc.
info Contains field name, path, schema, and AST information. Rarely needed.

TypeScript Typing

import { GraphQLResolveInfo } from "graphql";

type Resolver<TParent, TArgs, TContext, TResult> = (
  parent: TParent,
  args: TArgs,
  context: TContext,
  info: GraphQLResolveInfo,
) => TResult | Promise<TResult>;

// Example
const userResolver: Resolver<undefined, { id: string }, MyContext, User | null> = async (
  _,
  { id },
  { dataSources },
) => {
  return dataSources.usersAPI.getUser(id);
};

Resolver Map Structure

Resolvers are organized by type and field name:

const resolvers = {
  Query: {
    users: (_, __, { dataSources }) => dataSources.usersAPI.getAll(),
    user: (_, { id }, { dataSources }) => dataSources.usersAPI.getById(id),
  },

  Mutation: {
    createUser: (_, { input }, { dataSources }) => dataSources.usersAPI.create(input),
    updateUser: (_, { id, input }, { dataSources }) => dataSources.usersAPI.update(id, input),
    deleteUser: (_, { id }, { dataSources }) => dataSources.usersAPI.delete(id),
  },

  Subscription: {
    userCreated: {
      subscribe: (_, __, { pubsub }) => pubsub.asyncIterator(["USER_CREATED"]),
    },
  },

  User: {
    posts: (parent, _, { dataSources }) => dataSources.postsAPI.getByAuthor(parent.id),
    fullName: (parent) => `${parent.firstName} ${parent.lastName}`,
  },

  // Custom scalar
  DateTime: new GraphQLScalarType({
    name: "DateTime",
    serialize: (value) => value.toISOString(),
    parseValue: (value) => new Date(value),
  }),

  // Enum mapping (when values differ from schema)
  Status: {
    DRAFT: 0,
    PUBLISHED: 1,
    ARCHIVED: 2,
  },
};

Async Resolvers

Resolvers can return Promises. Apollo Server awaits them automatically.

const resolvers = {
  Query: {
    // Async/await pattern (recommended)
    user: async (_, { id }, { dataSources }) => {
      const user = await dataSources.usersAPI.getById(id);
      if (!user) {
        throw new GraphQLError("User not found", {
          extensions: { code: "NOT_FOUND" },
        });
      }
      return user;
    },

    // Promise pattern
    users: (_, __, { dataSources }) => {
      return dataSources.usersAPI.getAll();
    },

    // Parallel fetching
    dashboard: async (_, __, { dataSources }) => {
      const [users, posts, stats] = await Promise.all([
        dataSources.usersAPI.getAll(),
        dataSources.postsAPI.getRecent(),
        dataSources.analyticsAPI.getStats(),
      ]);
      return { users, posts, stats };
    },
  },
};

Field Resolvers

Field resolvers compute derived values or fetch related data:

const resolvers = {
  User: {
    // Computed field
    fullName: (parent) => `${parent.firstName} ${parent.lastName}`,

    // Related data (one-to-many)
    posts: async (parent, _, { dataSources }) => {
      return dataSources.postsAPI.getByAuthorId(parent.id);
    },

    // With arguments
    posts: async (parent, { limit, offset }, { dataSources }) => {
      return dataSources.postsAPI.getByAuthorId(parent.id, { limit, offset });
    },

    // Conditional fetching
    privateData: async (parent, _, { user }) => {
      if (user?.id !== parent.id) {
        return null;
      }
      return parent.privateData;
    },
  },

  Post: {
    // Related data (many-to-one)
    author: async (parent, _, { dataSources }) => {
      return dataSources.usersAPI.getById(parent.authorId);
    },
  },
};

Default Resolvers

Apollo Server provides default resolvers that return parent[fieldName]:

// Schema
type User {
  id: ID!
  name: String!
  email: String
}

// Resolvers - you don't need to write these
const resolvers = {
  User: {
    id: (parent) => parent.id,      // Default resolver handles this
    name: (parent) => parent.name,  // Default resolver handles this
    email: (parent) => parent.email, // Default resolver handles this
  },
};

// Only define resolvers when:
// 1. Field name differs from data property
// 2. Data needs transformation
// 3. Field requires fetching related data
const resolvers = {
  User: {
    fullName: (parent) => `${parent.first_name} ${parent.last_name}`,
  },
};

N+1 Problem

The N+1 problem occurs when fetching related data triggers separate queries for each item.

Problem Example

// Query: { users { posts { title } } }
// This causes:
// 1 query for users
// N queries for posts (one per user)

const resolvers = {
  Query: {
    users: () => db.query("SELECT * FROM users"), // 1 query
  },
  User: {
    posts: (parent) => db.query("SELECT * FROM posts WHERE author_id = ?", [parent.id]), // N queries
  },
};

Solution: DataLoader

import DataLoader from "dataloader";

// Create loader per request (in context)
const context = async () => ({
  loaders: {
    postsByAuthor: new DataLoader(async (authorIds) => {
      const posts = await db.query("SELECT * FROM posts WHERE author_id IN (?)", [authorIds]);
      // Return posts grouped by author_id in same order as input
      return authorIds.map((id) => posts.filter((p) => p.author_id === id));
    }),
  },
});

// Use loader in resolver
const resolvers = {
  User: {
    posts: (parent, _, { loaders }) => loaders.postsByAuthor.load(parent.id),
  },
};

Best Practices

Keep Resolvers Thin

// Bad - business logic in resolver
const resolvers = {
  Mutation: {
    createOrder: async (_, { input }, { dataSources, user }) => {
      const items = await dataSources.inventory.checkStock(input.items);
      const total = items.reduce((sum, item) => sum + item.price * item.qty, 0);
      const discount = user.isPremium ? total * 0.1 : 0;
      // ... more logic
    },
  },
};

// Good - delegate to service
const resolvers = {
  Mutation: {
    createOrder: async (_, { input }, { services, user }) => {
      return services.orders.create(input, user);
    },
  },
};

Handle Null Appropriately

const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources }) => {
      // Return null for not found (matches nullable schema)
      return dataSources.usersAPI.getById(id);
    },

    userRequired: async (_, { id }, { dataSources }) => {
      const user = await dataSources.usersAPI.getById(id);
      if (!user) {
        throw new GraphQLError("User not found");
      }
      return user;
    },
  },
};

Avoid Over-fetching in Parent

// Bad - fetching all data upfront
const resolvers = {
  Query: {
    user: async (_, { id }) => {
      const user = await db.user.findById(id);
      const posts = await db.posts.findByAuthor(id);
      const followers = await db.followers.findByUser(id);
      return { ...user, posts, followers };
    },
  },
};

// Good - fetch only when requested
const resolvers = {
  Query: {
    user: (_, { id }) => db.user.findById(id),
  },
  User: {
    posts: (parent) => db.posts.findByAuthor(parent.id),
    followers: (parent) => db.followers.findByUser(parent.id),
  },
};

Source: SKILL.md on GitHub

2 warnings16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The apollo-server skill is a safe and high-quality resource for building GraphQL servers with Apollo Server 5.x. It correctly instructs users on implementing authentication, handling errors, and optimizing performance using standard industry tools and official vendor resources.

  • Socket16d

    1 alert: gptAnomaly

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    6/7 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 1e1ba0e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 8 months ago
What it can do
Runs commands
metadata
{
  "author": "apollographql",
  "version": "1.0.0"
}
All 1 allowed tools
Bash(npm:*) Bash(npx:*) Bash(node:*) Read Write Edit Glob Grep
Other metadata
compatibility
Node.js v20+, TypeScript 4.7+. Works with Express v4/v5, standalone, Fastify, and serverless.
  • TypeScript
  • graphql
  • apollo-server
  • nodejs
  • express
  • fastify
  • resolvers
  • schema
  • authentication

README badge

README badge for apollographql/skills/apollo-server

Provides patterns and setup for building GraphQL servers with Apollo Server 5.x, covering schema definition, resolvers, context/authentication, and plugins. Use this for setting up new Apollo Server projects, writing resolvers, implementing auth, or troubleshooting performance issues across Node.js with Express, Fastify, or standalone deployments.

Generated from the current SKILL.md.

Does this skill cover Apollo Server 4.x or earlier versions?
No. This skill is specific to Apollo Server 5.x patterns and conventions. Apollo Server 4 and earlier have different APIs and setup procedures.
What frameworks can I use Apollo Server with?
Apollo Server 5 runs standalone, or integrates with Express v4/v5, Fastify, Koa, Next.js, and serverless environments. The skill includes examples for standalone and Express setups.
How do I handle authentication and authorization?
Implement authentication in the context creation (e.g., parsing tokens from headers) and authorization checks in individual resolvers using GraphQLError when access is denied.
Does this cover DataLoader and N+1 query prevention?
Yes. The skill references DataLoader as a standard practice for batching related queries and includes it in the data sources documentation.
What are the Node.js and TypeScript requirements?
Node.js v20+ and TypeScript 4.7+.

Generated from the current SKILL.md. These answers refresh after source changes.