| name | graphql-developer |
| description | [Extends solution-architect] GraphQL API specialist. Use for GraphQL schemas, Apollo Server/Federation, DataLoader, resolvers, subscriptions. Invoke alongside solution-architect for GraphQL API design. |
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)
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 User
User
ID
String
String
String
UserRole
posts Int, String PostConnection
DateTime
DateTime
UserRole
ADMIN
USER
GUEST
UserFilter
UserRole
String
DateTime
CreateUserInput
String
String
String
UserRole USER
UpdateUserInput
String
String
UserRole
UserConnection
UserEdge
PageInfo
Int
UserEdge
String
User
PageInfo
Boolean
Boolean
String
String
CreateUserPayload
User
Error
UpdateUserPayload
User
Error
DeleteUserPayload
Boolean
Error
Error
String
String
ErrorCode
ErrorCode
VALIDATION_ERROR
NOT_FOUND
UNAUTHORIZED
INTERNAL_ERROR
DateTime
Apollo Server Setup
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.>(),
express.(),
(server, {
: createContext,
}),
);
<>(
httpServer.({ : }, resolve)
);
.();
}
();
Resolvers with DataLoader
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')
: ,
: users[users. - ]
? .(users[users. - ].).()
: ,
},
totalCount,
};
},
: (_, __, { user }) => {
user;
},
},
: {
: (_, { input }, { dataSources }) => {
{
user = dataSources..(input);
{ user, : [] };
} (error) {
{
: ,
: [{ : error., : }],
};
}
},
: (_, { id, input }, { dataSources }) => {
{
user = dataSources..(id, input);
{ user, : [] };
} (error) {
{
: ,
: [{ : error., : }],
};
}
},
},
: {
: (parent, { first, after }, { dataSources }) => {
dataSources..(parent., { first, after });
},
},
};
DataLoader for N+1 Prevention
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 ids.map((id) => userMap.get(id) || null);
});
}
import { createUserLoader } from './dataSources/userLoader';
export interface Context {
: | ;
: {
: <, | >;
: ;
: ;
};
}
(): <> {
token = req..?.(, );
user = token ? (token) : ;
{
user,
: {
: (db),
: (db),
: (db),
},
};
}
Apollo Federation
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!
}
extend schema
@link ,
,
post ID Post
Post
Post
ID
String
String
User
User
ID
Post
.
///graphql
./users-subgraph/.graphql
///graphql
./posts-subgraph/.graphql
Subscriptions
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}`]);
},
},
},
};
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)
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,
: [...(existing?. || []), ...incoming.],
};
},
},
},
},
},
}),
});
{ useQuery, gql } ;
= gql`;
() {
{ data, loading, error, fetchMore } = (, {
: { : },
});
= () => {
(data?...) {
({
: {
: data...,
},
});
}
};
{ : data?...( e.), 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
Before Deploying
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