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.

referencescontext-and-auth.md

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

Context and Authentication Reference

Table of Contents

Context Function

The context function runs for every request and returns an object shared across all resolvers.

Standalone Server

import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";

const server = new ApolloServer({ typeDefs, resolvers });

const { url } = await startStandaloneServer(server, {
  context: async ({ req, res }) => {
    // req: IncomingMessage
    // res: ServerResponse
    return {
      token: req.headers.authorization,
    };
  },
});

Express Middleware

import { expressMiddleware } from "@as-integrations/express5";

app.use(
  "/graphql",
  cors(),
  express.json(),
  expressMiddleware(server, {
    context: async ({ req, res }) => {
      // req: express.Request
      // res: express.Response
      return {
        token: req.headers.authorization,
        ip: req.ip,
      };
    },
  }),
);

Context Initialization Order

const context = async ({ req }) => {
  // 1. Extract credentials
  const token = req.headers.authorization?.replace("Bearer ", "");

  // 2. Validate and decode (fail fast)
  let user = null;
  if (token) {
    try {
      user = await verifyToken(token);
    } catch (e) {
      // Don't throw - let resolvers handle auth
      console.warn("Invalid token:", e.message);
    }
  }

  // 3. Initialize data sources
  const dataSources = {
    usersAPI: new UsersDataSource(),
    postsAPI: new PostsDataSource(),
  };

  // 4. Return context object
  return { token, user, dataSources };
};

TypeScript Context Typing

Define and use a typed context for type safety:

import { ApolloServer } from "@apollo/server";

// Define context type
interface MyContext {
  token?: string;
  user?: {
    id: string;
    email: string;
    roles: string[];
  };
  dataSources: {
    usersAPI: UsersDataSource;
    postsAPI: PostsDataSource;
  };
}

// Pass to ApolloServer
const server = new ApolloServer<MyContext>({
  typeDefs,
  resolvers,
});

// Resolvers get typed context
const resolvers = {
  Query: {
    me: async (_, __, context: MyContext) => {
      if (!context.user) {
        throw new GraphQLError("Not authenticated");
      }
      return context.dataSources.usersAPI.getById(context.user.id);
    },
  },
};

Authentication Patterns

JWT Authentication

import jwt from "jsonwebtoken";

interface JwtPayload {
  userId: string;
  email: string;
  roles: string[];
}

const context = async ({ req }) => {
  const token = req.headers.authorization?.replace("Bearer ", "");

  let user = null;
  if (token) {
    try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload;
      user = {
        id: decoded.userId,
        email: decoded.email,
        roles: decoded.roles,
      };
    } catch (e) {
      // Token invalid or expired - user remains null
    }
  }

  return { user };
};

Session Authentication

import session from "express-session";

// Express setup
app.use(
  session({
    secret: process.env.SESSION_SECRET!,
    resave: false,
    saveUninitialized: false,
  }),
);

app.use(
  "/graphql",
  cors(),
  express.json(),
  expressMiddleware(server, {
    context: async ({ req }) => ({
      user: req.session.user,
      session: req.session,
    }),
  }),
);

// Login mutation
const resolvers = {
  Mutation: {
    login: async (_, { email, password }, { session, dataSources }) => {
      const user = await dataSources.usersAPI.authenticate(email, password);
      if (!user) {
        throw new GraphQLError("Invalid credentials");
      }
      session.user = user;
      return user;
    },

    logout: async (_, __, { session }) => {
      return new Promise((resolve, reject) => {
        session.destroy((err) => {
          if (err) reject(err);
          else resolve(true);
        });
      });
    },
  },
};

API Key Authentication

const context = async ({ req }) => {
  const apiKey = req.headers["x-api-key"];

  let client = null;
  if (apiKey) {
    client = await db.apiKeys.findOne({ key: apiKey, active: true });
  }

  return {
    client,
    isAuthenticated: !!client,
  };
};

Authorization Patterns

Field-Level Authorization

import { GraphQLError } from "graphql";

const resolvers = {
  User: {
    email: (parent, _, { user }) => {
      // Only return email to the user themselves or admins
      if (user?.id === parent.id || user?.roles.includes("admin")) {
        return parent.email;
      }
      return null;
    },

    privateData: (parent, _, { user }) => {
      if (!user) {
        throw new GraphQLError("Not authenticated", {
          extensions: { code: "UNAUTHENTICATED" },
        });
      }
      if (user.id !== parent.id) {
        throw new GraphQLError("Not authorized", {
          extensions: { code: "FORBIDDEN" },
        });
      }
      return parent.privateData;
    },
  },
};

Role-Based Authorization

// Helper function
function requireRole(user: User | null, roles: string[]): void {
  if (!user) {
    throw new GraphQLError("Not authenticated", {
      extensions: { code: "UNAUTHENTICATED" },
    });
  }

  const hasRole = roles.some((role) => user.roles.includes(role));
  if (!hasRole) {
    throw new GraphQLError(`Requires one of: ${roles.join(", ")}`, {
      extensions: { code: "FORBIDDEN" },
    });
  }
}

const resolvers = {
  Mutation: {
    deleteUser: async (_, { id }, { user, dataSources }) => {
      requireRole(user, ["admin"]);
      return dataSources.usersAPI.delete(id);
    },

    updatePost: async (_, { id, input }, { user, dataSources }) => {
      requireRole(user, ["admin", "editor"]);
      return dataSources.postsAPI.update(id, input);
    },
  },
};

Directive-Based Authorization

import { mapSchema, getDirective, MapperKind } from "@graphql-tools/utils";
import { defaultFieldResolver } from "graphql";

// Schema directive
const typeDefs = `#graphql
  directive @auth(requires: Role = USER) on FIELD_DEFINITION

  enum Role {
    ADMIN
    USER
    GUEST
  }

  type Query {
    users: [User!]! @auth(requires: ADMIN)
    me: User @auth
  }
`;

// Transform schema
function authDirectiveTransformer(schema) {
  return mapSchema(schema, {
    [MapperKind.OBJECT_FIELD]: (fieldConfig) => {
      const authDirective = getDirective(schema, fieldConfig, "auth")?.[0];

      if (authDirective) {
        const { requires } = authDirective;
        const { resolve = defaultFieldResolver } = fieldConfig;

        fieldConfig.resolve = async (source, args, context, info) => {
          if (!context.user) {
            throw new GraphQLError("Not authenticated");
          }

          if (requires && !context.user.roles.includes(requires)) {
            throw new GraphQLError(`Requires ${requires} role`);
          }

          return resolve(source, args, context, info);
        };
      }

      return fieldConfig;
    },
  });
}

Data Sources in Context

Creating Data Sources

interface MyContext {
  dataSources: {
    usersAPI: UsersDataSource;
    postsAPI: PostsDataSource;
  };
}

const context = async ({ req }): Promise<MyContext> => {
  return {
    dataSources: {
      usersAPI: new UsersDataSource(),
      postsAPI: new PostsDataSource(),
    },
  };
};

Passing User to Data Sources

class AuthenticatedDataSource extends RESTDataSource {
  private user?: User;

  setUser(user: User) {
    this.user = user;
  }

  override willSendRequest(path: string, request: AugmentedRequest) {
    if (this.user) {
      request.headers["x-user-id"] = this.user.id;
    }
  }
}

const context = async ({ req }) => {
  const user = await getUser(req.headers.authorization);

  const usersAPI = new UsersDataSource();
  if (user) {
    usersAPI.setUser(user);
  }

  return { user, dataSources: { usersAPI } };
};

Security Best Practices

Never Trust Client Input

const context = async ({ req }) => {
  // Bad - trusting client header
  const userId = req.headers["x-user-id"];

  // Good - verify token server-side
  const token = req.headers.authorization?.replace("Bearer ", "");
  const user = token ? await verifyToken(token) : null;

  return { user };
};

Don't Expose Internal Errors

const server = new ApolloServer({
  typeDefs,
  resolvers,
  formatError: (formattedError, error) => {
    // Log full error internally
    console.error(error);

    // Don't expose internal errors to clients
    if (formattedError.extensions?.code === "INTERNAL_SERVER_ERROR") {
      return {
        message: "Internal server error",
        extensions: { code: "INTERNAL_SERVER_ERROR" },
      };
    }

    return formattedError;
  },
});

Rate Limiting

import rateLimit from "express-rate-limit";

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per window
});

app.use("/graphql", limiter);

Depth Limiting

import depthLimit from "graphql-depth-limit";

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [depthLimit(10)],
});

Query Complexity

import { createComplexityLimitRule } from "graphql-validation-complexity";

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    createComplexityLimitRule(1000, {
      onCost: (cost) => console.log("Query cost:", cost),
    }),
  ],
});

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.