dist/plugins/api-graphql-apollo-server/skills/api-graphql-apollo-server/SKILL.md
GraphQL API server with Apollo Server — schema, resolvers, context, error handling, data sources, plugins
npx skillsauth add agents-inc/skills api-graphql-apollo-serverInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Use
@apollo/server(v5) for schema-first GraphQL APIs. Define schemas with SDL (typeDefs), implement field population with resolvers, share per-request state via thecontextfunction, and handle errors withGraphQLError+ extension codes. UsestartStandaloneServerfor quick setups or integrate with your HTTP framework for production. DataLoader solves the N+1 problem. Plugins hook into the request lifecycle for logging, auth, and performance.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST import from @apollo/server -- NOT the deprecated apollo-server or apollo-server-express packages)
(You MUST create new data source and DataLoader instances per request in the context function -- sharing across requests causes data leaks)
(You MUST throw GraphQLError (from graphql) with extension codes for client-facing errors -- generic Error exposes stack traces)
(You MUST use ApolloServerPluginDrainHttpServer when integrating with an HTTP framework -- without it the server doesn't shut down gracefully)
</critical_requirements>
Auto-detection: Apollo Server, @apollo/server, ApolloServer, startStandaloneServer, expressMiddleware, GraphQLError, typeDefs, resolvers, contextValue, DataLoader, RESTDataSource, @apollo/datasource-rest, ApolloServerPlugin, buildSubgraphSchema, @apollo/subgraph, graphql-ws, PubSub, formatError, gql tag
When to use:
graphql-ws)@apollo/subgraphWhen NOT to use:
Key patterns covered:
startStandaloneServer and framework middleware integrationparent, args, contextValue, info), and chainsGraphQLError, built-in codes, and formatErrorgraphql-ws and WebSocket server@apollo/subgraphDetailed Resources:
Apollo Server follows a schema-first approach: define your API contract in SDL, then implement resolvers to populate each field. The schema is the single source of truth for your API shape, documentation, and type system.
Core principles:
GraphQLError with extension codes communicates errors without leaking internalsUse Apollo Server when:
Use simpler approaches when:
Two approaches: startStandaloneServer for quick/simple setups, or framework middleware integration for production.
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
const server = new ApolloServer({ typeDefs, resolvers });
const DEFAULT_PORT = 4000;
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => ({
token: req.headers.authorization,
}),
listen: { port: DEFAULT_PORT },
});
Why good: minimal boilerplate for development, context function provides per-request auth state
For production with an HTTP framework, use expressMiddleware (from @as-integrations/express4 or @as-integrations/express5) with ApolloServerPluginDrainHttpServer for graceful shutdown.
See examples/core.md for both setup patterns with full TypeScript types.
Resolvers receive four arguments: parent (return value from parent resolver), args (field arguments), contextValue (shared per-request state), and info (operation metadata).
const resolvers = {
Query: {
user: async (_parent, args, contextValue) => {
return contextValue.dataSources.usersAPI.getUser(args.id);
},
},
User: {
posts: async (parent, _args, contextValue) => {
return contextValue.dataSources.postsAPI.getPostsByAuthor(parent.id);
},
},
};
Why good: parent chaining enables nested queries without client round-trips, data source access through context keeps resolvers thin
See examples/core.md for full resolver patterns with type safety.
The context function runs for every operation. Use it to create per-request data sources, extract auth, and set up DataLoaders.
interface MyContext {
token: string | undefined;
dataSources: {
usersAPI: UsersAPI;
postsAPI: PostsAPI;
};
}
const server = new ApolloServer<MyContext>({ typeDefs, resolvers });
Critical: Create new data source instances per request. Sharing instances across requests causes stale data and leaks between users.
See examples/core.md for complete context setup.
Throw GraphQLError (from the graphql package) with extension codes for structured client errors. Use formatError to sanitize errors before sending.
import { GraphQLError } from "graphql";
throw new GraphQLError("User not found", {
extensions: {
code: "USER_NOT_FOUND",
argumentName: "id",
},
});
Built-in codes (from ApolloServerErrorCode): GRAPHQL_PARSE_FAILED, GRAPHQL_VALIDATION_FAILED, BAD_USER_INPUT, INTERNAL_SERVER_ERROR, BAD_REQUEST, PERSISTED_QUERY_NOT_FOUND.
See examples/core.md for formatError, custom codes, and auth error patterns.
Wrap REST APIs with @apollo/datasource-rest for built-in caching, request deduplication, and HTTP method helpers.
import { RESTDataSource } from "@apollo/datasource-rest";
class MoviesAPI extends RESTDataSource {
override baseURL = "https://movies-api.example.com/";
async getMovie(id: string): Promise<Movie> {
return this.get<Movie>(`movies/${encodeURIComponent(id)}`);
}
}
Two-layer caching: (1) request deduplication prevents duplicate GET calls within a single operation, (2) HTTP response caching honors cache-control headers.
See examples/data-sources.md for RESTDataSource methods, custom caching, and willSendRequest.
Use DataLoader to batch and deduplicate database or API calls within a single GraphQL operation.
import DataLoader from "dataloader";
const userLoader = new DataLoader<string, User>(async (ids) => {
const users = await db.users.findMany({ where: { id: { in: [...ids] } } });
return ids.map((id) => users.find((u) => u.id === id) ?? new Error(`User ${id} not found`));
});
Critical: Create new DataLoader instances per request (in the context function). DataLoader caches results for the request lifetime -- sharing across requests serves stale data.
See examples/data-sources.md for DataLoader setup, batching patterns, and integration with context.
Plugins hook into server and request lifecycle events. Use them for logging, auth checks, performance tracing, and error reporting.
import type { ApolloServerPlugin } from "@apollo/server";
const loggingPlugin: ApolloServerPlugin<MyContext> = {
async requestDidStart(requestContext) {
const start = Date.now();
return {
async willSendResponse() {
const duration = Date.now() - start;
console.log(`Operation took ${duration}ms`);
},
};
},
};
Key lifecycle events: serverWillStart, requestDidStart, parsingDidStart, validationDidStart, executionDidStart, didEncounterErrors, willSendResponse.
See examples/advanced.md for complete plugin patterns.
Apollo Server does not include a built-in WebSocket transport. Use graphql-ws + ws for real-time subscriptions alongside your HTTP server.
import { WebSocketServer } from "ws";
import { useServer } from "graphql-ws/use/ws";
const wsServer = new WebSocketServer({ server: httpServer, path: "/subscriptions" });
const serverCleanup = useServer({ schema }, wsServer);
Note: startStandaloneServer does not support subscriptions -- use framework middleware integration instead. The in-memory PubSub from graphql-subscriptions is for development only; use a distributed pub/sub system in production.
See examples/advanced.md for complete subscription setup with drain plugins.
Use @apollo/subgraph to build a subgraph that participates in a federated supergraph. Entities use @key directives and __resolveReference resolvers.
import { buildSubgraphSchema } from "@apollo/subgraph";
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});
Key concepts: @key designates entity identity fields, __resolveReference fetches entities by their key fields, extend type contributes fields from other subgraphs.
See examples/advanced.md for federation schema and reference resolver patterns.
</patterns><red_flags>
High Priority:
apollo-server, apollo-server-express) -- use @apollo/server exclusivelyError from resolvers -- exposes stack traces to clients; use GraphQLError with extension codesApolloServerPluginDrainHttpServer when using framework integration -- server won't shut down gracefully, leaving connections hangingexpressMiddleware before server.start() -- throws an error; start() must complete firstMedium Priority:
c.req.params instead of resolver args -- bypasses GraphQL argument validationPubSub in production -- only works for a single server instance; use a distributed systemencodeURIComponent on dynamic URL segments in RESTDataSource -- path traversal vulnerabilityformatError to sanitize errors -- internal error messages and stack traces leak to clients in development modeGotchas & Edge Cases:
contextValue is shared: Never destructively modify contextValue in resolvers -- other resolvers in the same operation share the objectundefined: Triggers the default resolver to try accessing a property on the parent -- may cause unexpected behavior if the parent doesn't have that fieldNODE_ENV=production -- override with introspection: true if neededexpressMiddleware from @as-integrations/express4 or @as-integrations/express5 (not from @apollo/server/express4 which was removed)AsyncIterator from subscribe, not a direct value -- the resolve function (optional) transforms the event payloadwillResolveField and schemaDidLoadOrUpdate</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST import from @apollo/server -- NOT the deprecated apollo-server or apollo-server-express packages)
(You MUST create new data source and DataLoader instances per request in the context function -- sharing across requests causes data leaks)
(You MUST throw GraphQLError (from graphql) with extension codes for client-facing errors -- generic Error exposes stack traces)
(You MUST use ApolloServerPluginDrainHttpServer when integrating with an HTTP framework -- without it the server doesn't shut down gracefully)
Failure to follow these rules will cause data leaks between users, expose internal errors to clients, and prevent graceful shutdown.
</critical_reminders>
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Mapbox GL JS interactive maps - map initialization, markers, popups, sources, layers, expressions, clustering, 3D terrain, geocoding, directions
tools
Leaflet interactive maps - map setup, tile layers, markers, popups, GeoJSON, custom controls, plugins, clustering, events