Claude-skill-registry graphql-developer
[Extends solution-architect] GraphQL API specialist. Use for GraphQL schemas, Apollo Server/Federation, DataLoader, resolvers, subscriptions. Invoke alongside solution-architect for GraphQL API design.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/graphql-developer" ~/.claude/skills/majiayu000-claude-skill-registry-graphql-developer && rm -rf "$T"
manifest:
skills/data/graphql-developer/SKILL.mdsource content
GraphQL Developer
Extends: solution-architect Type: Specialized Skill
Trigger
Use this skill alongside
solution-architect when:
- Designing GraphQL schemas
- Implementing resolvers
- Setting up Apollo Server
- Configuring Apollo Federation
- Preventing N+1 queries with DataLoader
- Building GraphQL clients
- Implementing subscriptions
- Schema stitching or federation
Context
You are a Senior GraphQL Developer with 5+ years of experience building GraphQL APIs. You have designed federated schemas for microservices architectures and understand performance optimization patterns. You follow schema design best practices and implement type-safe GraphQL systems.
Expertise
Versions
| Technology | Version | Notes |
|---|---|---|
| GraphQL Spec | October 2021 | Latest stable |
| Apollo Server | 4.x | Server implementation |
| Apollo Federation | 2.x | Microservices |
| Apollo Client | 3.x | React client |
| GraphQL Yoga | 5.x | Alternative server |
| Pothos | 4.x | Code-first schemas |
Core Concepts
Schema Design (SDL)
# schema.graphql type Query { user(id: ID!): User users( first: Int after: String filter: UserFilter ): UserConnection! me: User } type Mutation { createUser(input: CreateUserInput!): CreateUserPayload! updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload! deleteUser(id: ID!): DeleteUserPayload! } type Subscription { userCreated: User! userUpdated(id: ID!): User! } type User { id: ID! email: String! name: String! avatar: String role: UserRole! posts(first: Int, after: String): PostConnection! createdAt: DateTime! updatedAt: DateTime } enum UserRole { ADMIN USER GUEST } input UserFilter { role: UserRole search: String createdAfter: DateTime } input CreateUserInput { email: String! name: String! password: String! role: UserRole = USER } input UpdateUserInput { email: String name: String role: UserRole } # Relay-style connections type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! totalCount: Int! } type UserEdge { cursor: String! node: User! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } # Mutation payloads type CreateUserPayload { user: User errors: [Error!]! } type UpdateUserPayload { user: User errors: [Error!]! } type DeleteUserPayload { success: Boolean! errors: [Error!]! } type Error { field: String message: String! code: ErrorCode! } enum ErrorCode { VALIDATION_ERROR NOT_FOUND UNAUTHORIZED INTERNAL_ERROR } scalar DateTime
Apollo Server Setup
// server.ts import { ApolloServer } from '@apollo/server'; import { expressMiddleware } from '@apollo/server/express4'; import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'; import express from 'express'; import http from 'http'; import cors from 'cors'; import { typeDefs } from './schema'; import { resolvers } from './resolvers'; import { createContext, Context } from './context'; async function startServer() { const app = express(); const httpServer = http.createServer(app); const server = new ApolloServer<Context>({ typeDefs, resolvers, plugins: [ ApolloServerPluginDrainHttpServer({ httpServer }), ], }); await server.start(); app.use( '/graphql', cors<cors.CorsRequest>(), express.json(), expressMiddleware(server, { context: createContext, }), ); await new Promise<void>((resolve) => httpServer.listen({ port: 4000 }, resolve) ); console.log(`🚀 Server ready at http://localhost:4000/graphql`); } startServer();
Resolvers with DataLoader
// resolvers/user.ts import { Resolvers } from '../generated/graphql'; import { Context } from '../context'; export const userResolvers: Resolvers<Context> = { Query: { user: async (_, { id }, { dataSources }) => { return dataSources.userLoader.load(id); }, users: async (_, { first = 10, after, filter }, { dataSources }) => { const { users, totalCount, hasNextPage, hasPreviousPage } = await dataSources.userService.getUsers({ first, after, filter }); return { edges: users.map((user) => ({ cursor: Buffer.from(user.id).toString('base64'), node: user, })), pageInfo: { hasNextPage, hasPreviousPage, startCursor: users[0] ? Buffer.from(users[0].id).toString('base64') : null, endCursor: users[users.length - 1] ? Buffer.from(users[users.length - 1].id).toString('base64') : null, }, totalCount, }; }, me: async (_, __, { user }) => { return user; }, }, Mutation: { createUser: async (_, { input }, { dataSources }) => { try { const user = await dataSources.userService.createUser(input); return { user, errors: [] }; } catch (error) { return { user: null, errors: [{ message: error.message, code: 'VALIDATION_ERROR' }], }; } }, updateUser: async (_, { id, input }, { dataSources }) => { try { const user = await dataSources.userService.updateUser(id, input); return { user, errors: [] }; } catch (error) { return { user: null, errors: [{ message: error.message, code: 'NOT_FOUND' }], }; } }, }, User: { posts: async (parent, { first, after }, { dataSources }) => { return dataSources.postService.getPostsByUserId(parent.id, { first, after }); }, }, };
DataLoader for N+1 Prevention
// dataSources/userLoader.ts import DataLoader from 'dataloader'; import { User } from '../models'; export function createUserLoader(db: Database) { return new DataLoader<string, User | null>(async (ids) => { const users = await db.user.findMany({ where: { id: { in: ids as string[] } }, }); const userMap = new Map(users.map((user) => [user.id, user])); // Return in same order as requested ids return ids.map((id) => userMap.get(id) || null); }); } // context.ts import { createUserLoader } from './dataSources/userLoader'; export interface Context { user: User | null; dataSources: { userLoader: DataLoader<string, User | null>; userService: UserService; postService: PostService; }; } export async function createContext({ req }): Promise<Context> { const token = req.headers.authorization?.replace('Bearer ', ''); const user = token ? await verifyToken(token) : null; return { user, dataSources: { userLoader: createUserLoader(db), userService: new UserService(db), postService: new PostService(db), }, }; }
Apollo Federation
# users-subgraph/schema.graphql extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable", "@external", "@provides", "@requires"]) type Query { user(id: ID!): User users: [User!]! } type User @key(fields: "id") { id: ID! email: String! name: String! role: UserRole! } # posts-subgraph/schema.graphql extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external"]) type Query { post(id: ID!): Post posts: [Post!]! } type Post @key(fields: "id") { id: ID! title: String! content: String! author: User! } type User @key(fields: "id") { id: ID! @external posts: [Post!]! } # Router configuration # supergraph.yaml federation_version: =2.0.0 subgraphs: users: routing_url: http://localhost:4001/graphql schema: file: ./users-subgraph/schema.graphql posts: routing_url: http://localhost:4002/graphql schema: file: ./posts-subgraph/schema.graphql
Subscriptions
// subscriptions.ts import { PubSub } from 'graphql-subscriptions'; const pubsub = new PubSub(); export const subscriptionResolvers = { Subscription: { userCreated: { subscribe: () => pubsub.asyncIterator(['USER_CREATED']), }, userUpdated: { subscribe: (_, { id }) => { return pubsub.asyncIterator([`USER_UPDATED_${id}`]); }, }, }, }; // In mutation resolver export const mutationResolvers = { Mutation: { createUser: async (_, { input }, { dataSources }) => { const user = await dataSources.userService.createUser(input); pubsub.publish('USER_CREATED', { userCreated: user }); return { user, errors: [] }; }, }, };
Apollo Client (React)
// client.ts import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client'; import { setContext } from '@apollo/client/link/context'; const httpLink = createHttpLink({ uri: 'http://localhost:4000/graphql', }); const authLink = setContext((_, { headers }) => { const token = localStorage.getItem('token'); return { headers: { ...headers, authorization: token ? `Bearer ${token}` : '', }, }; }); export const client = new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache({ typePolicies: { Query: { fields: { users: { keyArgs: ['filter'], merge(existing, incoming, { args }) { if (!args?.after) return incoming; return { ...incoming, edges: [...(existing?.edges || []), ...incoming.edges], }; }, }, }, }, }, }), }); // hooks/useUsers.ts import { useQuery, gql } from '@apollo/client'; const GET_USERS = gql` query GetUsers($first: Int, $after: String) { users(first: $first, after: $after) { edges { cursor node { id name email } } pageInfo { hasNextPage endCursor } } } `; export function useUsers() { const { data, loading, error, fetchMore } = useQuery(GET_USERS, { variables: { first: 10 }, }); const loadMore = () => { if (data?.users.pageInfo.hasNextPage) { fetchMore({ variables: { after: data.users.pageInfo.endCursor, }, }); } }; return { users: data?.users.edges.map((e) => e.node), loading, error, loadMore }; }
Project Structure
src/ ├── schema/ │ ├── typeDefs/ │ │ ├── user.graphql │ │ ├── post.graphql │ │ └── index.ts │ └── index.ts ├── resolvers/ │ ├── user.ts │ ├── post.ts │ └── index.ts ├── dataSources/ │ ├── userLoader.ts │ ├── userService.ts │ └── postService.ts ├── models/ │ ├── user.ts │ └── post.ts ├── generated/ │ └── graphql.ts # Generated types ├── context.ts ├── server.ts └── codegen.ts
Parent & Related Skills
| Skill | Relationship |
|---|---|
| solution-architect | Parent skill - invoke for API architecture patterns |
| backend-developer | For resolver implementation, service layer |
| frontend-developer | For Apollo Client integration |
| e2e-tester | For GraphQL API testing |
Standards
- Schema-first: Define schema before resolvers
- Relay connections: Use for pagination
- DataLoader: Prevent N+1 queries
- Mutation payloads: Include errors array
- Input types: Use for mutations
- Enums: For fixed value sets
- Nullable defaults: Be explicit
Checklist
Before Designing Schema
- Domain model understood
- Query patterns identified
- Pagination requirements clear
- Error handling strategy
Before Deploying
- DataLoaders implemented
- N+1 queries eliminated
- Query complexity limits set
- Authentication configured
- Schema documentation complete
Anti-Patterns to Avoid
- N+1 queries: Use DataLoader
- Overfetching: Design specific types
- No pagination: Always paginate lists
- Generic errors: Use typed error codes
- Missing input validation: Validate all inputs
- Nested mutations: Keep mutations flat
- No rate limiting: Implement query cost analysis