Senior GraphQL Specialist
Expert GraphQL API design and architecture skill for building scalable, type-safe APIs with Apollo Server, Federation, and modern GraphQL patterns.
Overview
This skill provides comprehensive GraphQL development capabilities including schema design, resolver implementation, federation architecture, real-time subscriptions, and performance optimization through DataLoader patterns.
Time Savings: 50%+ reduction in GraphQL API development time through schema generation, resolver scaffolding, and automated federation setup.
Quality Improvement: 40%+ improvement in API consistency through schema-first development, type safety enforcement, and automated best practices.
Core Capabilities
Schema Architecture
- Schema-first design methodology
- Type system design (scalars, enums, interfaces, unions)
- Input type and argument patterns
- Custom directive implementation
- Schema stitching and composition
Resolver Development
- Resolver pattern implementation
- Context and middleware integration
- Authentication/authorization in resolvers
- Error handling and formatting
- N+1 query prevention with DataLoader
Apollo Federation
- Supergraph architecture design
- Subgraph creation and entity definitions
@key, @external, @requires directive usage
- Gateway configuration
- Schema composition validation
Performance Optimization
- Query complexity analysis and limiting
- Depth limiting implementation
- Caching strategies (Apollo Cache, Redis)
- Batching with DataLoader
- Persisted queries
Real-time Features
- Subscription implementation
- WebSocket configuration
- PubSub patterns
- Filtered subscriptions
Quick Start
# Analyze existing GraphQL schema
python scripts/schema_analyzer.py schema.graphql --output json
# Generate resolvers from schema
python scripts/resolver_generator.py schema.graphql --output src/resolvers
# Scaffold Apollo Federation subgraph
python scripts/federation_scaffolder.py users-service --entities User,Profile
Key Workflows
1. Schema-First API Design
Goal: Design a type-safe GraphQL schema following best practices.
Steps:
Analyze Requirements
- Identify domain entities and relationships
- Map CRUD operations to queries/mutations
- Define subscription needs for real-time features
Design Schema
# Types with clear naming conventions
type User {
id: ID!
email: String!
profile: Profile
posts(first: Int, after: String): PostConnection!
createdAt: DateTime!
}
# Relay-style pagination
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type PostEdge {
node: Post!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
# Input types for mutations
input CreateUserInput {
email: String!
name: String!
password: String!
}
# Clear query/mutation organization
type Query {
user(id: ID!): User
users(first: Int, after: String): UserConnection!
me: User
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
deleteUser(id: ID!): DeleteUserPayload!
}
# Subscription for real-time
type Subscription {
userCreated: User!
postPublished(authorId: ID): Post!
}
Validate Schema
python scripts/schema_analyzer.py schema.graphql --validate
Generate Resolvers
python scripts/resolver_generator.py schema.graphql --output src/resolvers --typescript
Success Criteria:
- Schema passes validation
- All types have descriptions
- Relay pagination implemented for lists
- Input types for all mutations
- Clear naming conventions followed
2. DataLoader Implementation for N+1 Prevention
Goal: Eliminate N+1 queries using DataLoader batching.
Problem Example:
# This query would cause N+1 without DataLoader
query {
posts { # 1 query for posts
author { # N queries for authors (one per post!)
name
}
}
}
Solution:
Create DataLoader Factory
// src/dataloaders/index.ts
import DataLoader from 'dataloader';
import { prisma } from '../lib/prisma';
export const createLoaders = () => ({
userLoader: new DataLoader<string, User>(async (userIds) => {
const users = await prisma.user.findMany({
where: { id: { in: [...userIds] } }
});
// Return in same order as requested IDs
const userMap = new Map(users.map(u => [u.id, u]));
return userIds.map(id => userMap.get(id) || null);
}),
postsByAuthorLoader: new DataLoader<string, Post[]>(async (authorIds) => {
const posts = await prisma.post.findMany({
where: { authorId: { in: [...authorIds] } }
});
// Group posts by authorId
const postMap = new Map<string, Post[]>();
posts.forEach(post => {
const existing = postMap.get(post.authorId) || [];
existing.push(post);
postMap.set(post.authorId, existing);
});
return authorIds.map(id => postMap.get(id) || []);
}),
});
export type Loaders = ReturnType<typeof createLoaders>;
Add Loaders to Context
// src/server.ts
import { createLoaders } from './dataloaders';
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({
user: authenticateToken(req),
loaders: createLoaders(), // Fresh loaders per request
}),
});
Use in Resolvers
// src/resolvers/post.resolver.ts
export const PostResolvers = {
Post: {
author: (parent, _, { loaders }) => {
return loaders.userLoader.load(parent.authorId);
},
},
};
Verify Batching
- Enable query logging
- Run test query
- Confirm single batch query instead of N queries
Success Criteria:
- Batch queries visible in logs
- Query count reduced from N+1 to 2
- Response time improved significantly
- DataLoader cache cleared per request
3. Apollo Federation Setup
Goal: Build a federated supergraph from multiple subgraphs.
Architecture:
┌─────────────────────────────────────────────────┐
│ Apollo Gateway │
│ (Schema Composition) │
└─────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Users │ │ Posts │ │ Comments │
│ Subgraph │ │ Subgraph │ │ Subgraph │
└─────────────┘ └─────────────┘ └─────────────┘
Steps:
Scaffold Subgraphs
# Create users subgraph
python scripts/federation_scaffolder.py users-service \
--entities User,Profile \
--port 4001
# Create posts subgraph
python scripts/federation_scaffolder.py posts-service \
--entities Post \
--references User \
--port 4002
# Create comments subgraph
python scripts/federation_scaffolder.py comments-service \
--entities Comment \
--references User,Post \
--port 4003
Define Entity References
# users-service/schema.graphql
type User @key(fields: "id") {
id: ID!
email: String!
name: String!
profile: Profile
}
# posts-service/schema.graphql
type Post @key(fields: "id") {
id: ID!
title: String!
content: String!
author: User!
}
# Extend User to add posts field
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post!]!
}
# comments-service/schema.graphql
type Comment @key(fields: "id") {
id: ID!
content: String!
author: User!
post: Post!
}
extend type Post @key(fields: "id") {
id: ID! @external
comments: [Comment!]!
}
Implement Reference Resolvers
// posts-service/resolvers.ts
export const resolvers = {
User: {
__resolveReference: async (user, { dataSources }) => {
// Return only the fields this subgraph owns
return { id: user.id };
},
posts: async (user, _, { dataSources }) => {
return dataSources.postsAPI.getPostsByAuthor(user.id);
},
},
Post: {
__resolveReference: async (post, { dataSources }) => {
return dataSources.postsAPI.getPost(post.id);
},
author: (post) => {
// Return reference for gateway to resolve
return { __typename: 'User', id: post.authorId };
},
},
};
Configure Gateway
// gateway/index.ts
import { ApolloGateway, IntrospectAndCompose } from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'users', url: 'http://localhost:4001/graphql' },
{ name: 'posts', url: 'http://localhost:4002/graphql' },
{ name: 'comments', url: 'http://localhost:4003/graphql' },
],
}),
});
const server = new ApolloServer({ gateway });
Test Federated Query
query FederatedQuery {
user(id: "123") {
id
name
posts {
id
title
comments {
content
author {
name # Resolves back to users subgraph
}
}
}
}
}
Success Criteria:
- All subgraphs start without errors
- Schema composition succeeds
- Cross-subgraph queries resolve correctly
- Entity references work bidirectionally
4. Real-time Subscriptions
Goal: Implement GraphQL subscriptions for real-time updates.
Steps:
Configure WebSocket Server
// src/server.ts
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
const httpServer = createServer(app);
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql',
});
const serverCleanup = useServer(
{
schema,
context: (ctx) => ({
user: authenticateWebSocket(ctx.connectionParams),
}),
},
wsServer
);
const server = new ApolloServer({
schema,
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
async serverWillStart() {
return {
async drainServer() {
await serverCleanup.dispose();
},
};
},
},
],
});
Implement PubSub
// src/pubsub.ts
import { PubSub } from 'graphql-subscriptions';
import { RedisPubSub } from 'graphql-redis-subscriptions';
// For production, use Redis PubSub
export const pubsub = new RedisPubSub({
connection: process.env.REDIS_URL,
});
// Event types
export const EVENTS = {
POST_CREATED: 'POST_CREATED',
POST_UPDATED: 'POST_UPDATED',
COMMENT_ADDED: 'COMMENT_ADDED',
USER_ONLINE: 'USER_ONLINE',
};
Define Subscription Schema
type Subscription {
postCreated: Post!
postUpdated(id: ID!): Post!
commentAdded(postId: ID!): Comment!
userPresence(roomId: ID!): UserPresenceEvent!
}
type UserPresenceEvent {
user: User!
status: PresenceStatus!
}
enum PresenceStatus {
ONLINE
OFFLINE
AWAY
}
Implement Subscription Resolvers
// src/resolvers/subscription.resolver.ts
import { withFilter } from 'graphql-subscriptions';
import { pubsub, EVENTS } from '../pubsub';
export const SubscriptionResolvers = {
Subscription: {
postCreated: {
subscribe: () => pubsub.asyncIterator([EVENTS.POST_CREATED]),
},
postUpdated: {
subscribe: withFilter(
() => pubsub.asyncIterator([EVENTS.POST_UPDATED]),
(payload, variables) => {
return payload.postUpdated.id === variables.id;
}
),
},
commentAdded: {
subscribe: withFilter(
() => pubsub.asyncIterator([EVENTS.COMMENT_ADDED]),
(payload, variables, context) => {
// Only notify if user has access to the post
return payload.commentAdded.postId === variables.postId;
}
),
},
},
};
Publish Events
// src/resolvers/mutation.resolver.ts
export const MutationResolvers = {
Mutation: {
createPost: async (_, { input }, { user, prisma }) => {
const post = await prisma.post.create({
data: { ...input, authorId: user.id },
});
// Publish to subscribers
await pubsub.publish(EVENTS.POST_CREATED, { postCreated: post });
return { post };
},
addComment: async (_, { input }, { user, prisma }) => {
const comment = await prisma.comment.create({
data: { ...input, authorId: user.id },
});
// Publish to subscribers watching this post
await pubsub.publish(EVENTS.COMMENT_ADDED, { commentAdded: comment });
return { comment };
},
},
};
Success Criteria:
- WebSocket connection established
- Subscriptions receive real-time updates
- Filtering works correctly
- Connection cleanup on disconnect
- Production-ready with Redis PubSub
Python Tools
schema_analyzer.py
Purpose: Analyze GraphQL schemas for quality, complexity, and best practices.
Usage:
# Basic analysis
python scripts/schema_analyzer.py schema.graphql
# JSON output for tooling
python scripts/schema_analyzer.py schema.graphql --output json
# Validate against best practices
python scripts/schema_analyzer.py schema.graphql --validate
# Analyze complexity and depth
python scripts/schema_analyzer.py schema.graphql --complexity
Features:
- Type system analysis (types, interfaces, unions, enums)
- Query/mutation/subscription inventory
- Complexity scoring per operation
- Deprecation tracking
- Naming convention validation
- Description coverage checking
- Circular reference detection
resolver_generator.py
Purpose: Generate TypeScript resolvers from GraphQL schema.
Usage:
# Generate resolvers
python scripts/resolver_generator.py schema.graphql --output src/resolvers
# With DataLoader integration
python scripts/resolver_generator.py schema.graphql --output src/resolvers --dataloader
# For specific types only
python scripts/resolver_generator.py schema.graphql --output src/resolvers --types User,Post
# Generate with tests
python scripts/resolver_generator.py schema.graphql --output src/resolvers --tests
Generated Output:
- Resolver files per type
- Type definitions
- DataLoader factories
- Context type definitions
- Jest test stubs
federation_scaffolder.py
Purpose: Scaffold Apollo Federation subgraphs with proper entity definitions.
Usage:
# Create new subgraph
python scripts/federation_scaffolder.py users-service --entities User,Profile
# With entity references
python scripts/federation_scaffolder.py posts-service --entities Post --references User
# Full service with Docker
python scripts/federation_scaffolder.py comments-service --entities Comment --docker --port 4003
# Scaffold gateway
python scripts/federation_scaffolder.py gateway --subgraphs users:4001,posts:4002,comments:4003
Generated Structure:
service-name/
├── src/
│ ├── schema.graphql # Federation schema
│ ├── resolvers/ # Type resolvers
│ ├── dataloaders/ # DataLoader factories
│ ├── datasources/ # Data access layer
│ └── index.ts # Apollo Server setup
├── tests/ # Jest tests
├── Dockerfile # Container definition
├── docker-compose.yml # Local development
└── package.json
Best Practices
Schema Design
- Use descriptive names (avoid abbreviations)
- Document all types and fields
- Implement Relay-style pagination for lists
- Use input types for mutations
- Return payload types from mutations (not raw types)
- Version breaking changes with new fields (not removal)
Resolver Patterns
- Keep resolvers thin (delegate to services)
- Use DataLoader for all batch-able relations
- Implement proper error handling
- Add authentication at resolver level
- Log slow resolvers for optimization
Federation
- Define clear subgraph boundaries
- Minimize cross-subgraph queries
- Use
@requires sparingly
- Implement proper health checks
- Version subgraph schemas independently
Performance
- Implement query complexity limits
- Use persisted queries in production
- Cache with appropriate TTLs
- Monitor resolver execution time
- Implement query depth limiting
References
Reference Files
references/schema-patterns.md - Schema design patterns and conventions
references/federation-guide.md - Apollo Federation architecture guide
references/performance-optimization.md - GraphQL performance best practices
External Resources
Version: 1.0.0
Last Updated: 2025-12-16
Skill Type: Engineering specialist
Python Tools: 3 (schema_analyzer.py, resolver_generator.py, federation_scaffolder.py)
1---2name: senior-graphql3description: GraphQL API design specialist for schema architecture, resolver patterns, federation, and performance optimization4license: MIT5---6
7# Senior GraphQL Specialist
8
9Expert GraphQL API design and architecture skill for building scalable, type-safe APIs with Apollo Server, Federation, and modern GraphQL patterns.
10
11## Overview
12
13This skill provides comprehensive GraphQL development capabilities including schema design, resolver implementation, federation architecture, real-time subscriptions, and performance optimization through DataLoader patterns.
14
15**Time Savings:** 50%+ reduction in GraphQL API development time through schema generation, resolver scaffolding, and automated federation setup.
16
17**Quality Improvement:** 40%+ improvement in API consistency through schema-first development, type safety enforcement, and automated best practices.
18
19## Core Capabilities
20
21### Schema Architecture
22- Schema-first design methodology
23- Type system design (scalars, enums, interfaces, unions)
24- Input type and argument patterns
25- Custom directive implementation
26- Schema stitching and composition
27
28### Resolver Development
29- Resolver pattern implementation
30- Context and middleware integration
31- Authentication/authorization in resolvers
32- Error handling and formatting
33- N+1 query prevention with DataLoader
34
35### Apollo Federation
36- Supergraph architecture design
37- Subgraph creation and entity definitions
38- `@key`, `@external`, `@requires` directive usage
39- Gateway configuration
40- Schema composition validation
41
42### Performance Optimization
43- Query complexity analysis and limiting
44- Depth limiting implementation
45- Caching strategies (Apollo Cache, Redis)
46- Batching with DataLoader
47- Persisted queries
48
49### Real-time Features
50- Subscription implementation
51- WebSocket configuration
52- PubSub patterns
53- Filtered subscriptions
54
55## Quick Start
56
57```bash
58# Analyze existing GraphQL schema
59python scripts/schema_analyzer.py schema.graphql --output json
60
61# Generate resolvers from schema
62python scripts/resolver_generator.py schema.graphql --output src/resolvers
63
64# Scaffold Apollo Federation subgraph
65python scripts/federation_scaffolder.py users-service --entities User,Profile
66```
67
68## Key Workflows
69
70### 1. Schema-First API Design
71
72**Goal:** Design a type-safe GraphQL schema following best practices.
73
74**Steps:**
75
761. **Analyze Requirements**
77 - Identify domain entities and relationships
78 - Map CRUD operations to queries/mutations
79 - Define subscription needs for real-time features
80
812. **Design Schema**
82 ```graphql
83 # Types with clear naming conventions
84 type User {
85 id: ID!
86 email: String!
87 profile: Profile
88 posts(first: Int, after: String): PostConnection!
89 createdAt: DateTime!
90 }
91
92 # Relay-style pagination
93 type PostConnection {
94 edges: [PostEdge!]!
95 pageInfo: PageInfo!
96 totalCount: Int!
97 }
98
99 type PostEdge {
100 node: Post!
101 cursor: String!
102 }
103
104 type PageInfo {
105 hasNextPage: Boolean!
106 hasPreviousPage: Boolean!
107 startCursor: String
108 endCursor: String
109 }
110
111 # Input types for mutations
112 input CreateUserInput {
113 email: String!
114 name: String!
115 password: String!
116 }
117
118 # Clear query/mutation organization
119 type Query {
120 user(id: ID!): User
121 users(first: Int, after: String): UserConnection!
122 me: User
123 }
124
125 type Mutation {
126 createUser(input: CreateUserInput!): CreateUserPayload!
127 updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
128 deleteUser(id: ID!): DeleteUserPayload!
129 }
130
131 # Subscription for real-time
132 type Subscription {
133 userCreated: User!
134 postPublished(authorId: ID): Post!
135 }
136 ```
137
1383. **Validate Schema**
139 ```bash
140 python scripts/schema_analyzer.py schema.graphql --validate
141 ```
142
1434. **Generate Resolvers**
144 ```bash
145 python scripts/resolver_generator.py schema.graphql --output src/resolvers --typescript
146 ```
147
148**Success Criteria:**
149- Schema passes validation
150- All types have descriptions
151- Relay pagination implemented for lists
152- Input types for all mutations
153- Clear naming conventions followed
154
155### 2. DataLoader Implementation for N+1 Prevention
156
157**Goal:** Eliminate N+1 queries using DataLoader batching.
158
159**Problem Example:**
160```graphql
161# This query would cause N+1 without DataLoader
162query {
163 posts { # 1 query for posts
164 author { # N queries for authors (one per post!)
165 name
166 }
167 }
168}
169```
170
171**Solution:**
172
1731. **Create DataLoader Factory**
174 ```typescript
175 // src/dataloaders/index.ts
176 import DataLoader from 'dataloader';
177 import { prisma } from '../lib/prisma';
178
179 export const createLoaders = () => ({
180 userLoader: new DataLoader<string, User>(async (userIds) => {
181 const users = await prisma.user.findMany({
182 where: { id: { in: [...userIds] } }
183 });
184 // Return in same order as requested IDs
185 const userMap = new Map(users.map(u => [u.id, u]));
186 return userIds.map(id => userMap.get(id) || null);
187 }),
188
189 postsByAuthorLoader: new DataLoader<string, Post[]>(async (authorIds) => {
190 const posts = await prisma.post.findMany({
191 where: { authorId: { in: [...authorIds] } }
192 });
193 // Group posts by authorId
194 const postMap = new Map<string, Post[]>();
195 posts.forEach(post => {
196 const existing = postMap.get(post.authorId) || [];
197 existing.push(post);
198 postMap.set(post.authorId, existing);
199 });
200 return authorIds.map(id => postMap.get(id) || []);
201 }),
202 });
203
204 export type Loaders = ReturnType<typeof createLoaders>;
205 ```
206
2072. **Add Loaders to Context**
208 ```typescript
209 // src/server.ts
210 import { createLoaders } from './dataloaders';
211
212 const server = new ApolloServer({
213 typeDefs,
214 resolvers,
215 context: ({ req }) => ({
216 user: authenticateToken(req),
217 loaders: createLoaders(), // Fresh loaders per request
218 }),
219 });
220 ```
221
2223. **Use in Resolvers**
223 ```typescript
224 // src/resolvers/post.resolver.ts
225 export const PostResolvers = {
226 Post: {
227 author: (parent, _, { loaders }) => {
228 return loaders.userLoader.load(parent.authorId);
229 },
230 },
231 };
232 ```
233
2344. **Verify Batching**
235 - Enable query logging
236 - Run test query
237 - Confirm single batch query instead of N queries
238
239**Success Criteria:**
240- Batch queries visible in logs
241- Query count reduced from N+1 to 2
242- Response time improved significantly
243- DataLoader cache cleared per request
244
245### 3. Apollo Federation Setup
246
247**Goal:** Build a federated supergraph from multiple subgraphs.
248
249**Architecture:**
250```
251┌─────────────────────────────────────────────────┐
252│ Apollo Gateway │
253│ (Schema Composition) │
254└─────────────────────────────────────────────────┘
255 │ │ │
256 ▼ ▼ ▼
257┌─────────────┐ ┌─────────────┐ ┌─────────────┐
258│ Users │ │ Posts │ │ Comments │
259│ Subgraph │ │ Subgraph │ │ Subgraph │
260└─────────────┘ └─────────────┘ └─────────────┘
261```
262
263**Steps:**
264
2651. **Scaffold Subgraphs**
266 ```bash
267 # Create users subgraph
268 python scripts/federation_scaffolder.py users-service \
269 --entities User,Profile \
270 --port 4001
271
272 # Create posts subgraph
273 python scripts/federation_scaffolder.py posts-service \
274 --entities Post \
275 --references User \
276 --port 4002
277
278 # Create comments subgraph
279 python scripts/federation_scaffolder.py comments-service \
280 --entities Comment \
281 --references User,Post \
282 --port 4003
283 ```
284
2852. **Define Entity References**
286 ```graphql
287 # users-service/schema.graphql
288 type User @key(fields: "id") {
289 id: ID!
290 email: String!
291 name: String!
292 profile: Profile
293 }
294
295 # posts-service/schema.graphql
296 type Post @key(fields: "id") {
297 id: ID!
298 title: String!
299 content: String!
300 author: User!
301 }
302
303 # Extend User to add posts field
304 extend type User @key(fields: "id") {
305 id: ID! @external
306 posts: [Post!]!
307 }
308
309 # comments-service/schema.graphql
310 type Comment @key(fields: "id") {
311 id: ID!
312 content: String!
313 author: User!
314 post: Post!
315 }
316
317 extend type Post @key(fields: "id") {
318 id: ID! @external
319 comments: [Comment!]!
320 }
321 ```
322
3233. **Implement Reference Resolvers**
324 ```typescript
325 // posts-service/resolvers.ts
326 export const resolvers = {
327 User: {
328 __resolveReference: async (user, { dataSources }) => {
329 // Return only the fields this subgraph owns
330 return { id: user.id };
331 },
332 posts: async (user, _, { dataSources }) => {
333 return dataSources.postsAPI.getPostsByAuthor(user.id);
334 },
335 },
336 Post: {
337 __resolveReference: async (post, { dataSources }) => {
338 return dataSources.postsAPI.getPost(post.id);
339 },
340 author: (post) => {
341 // Return reference for gateway to resolve
342 return { __typename: 'User', id: post.authorId };
343 },
344 },
345 };
346 ```
347
3484. **Configure Gateway**
349 ```typescript
350 // gateway/index.ts
351 import { ApolloGateway, IntrospectAndCompose } from '@apollo/gateway';
352 import { ApolloServer } from '@apollo/server';
353
354 const gateway = new ApolloGateway({
355 supergraphSdl: new IntrospectAndCompose({
356 subgraphs: [
357 { name: 'users', url: 'http://localhost:4001/graphql' },
358 { name: 'posts', url: 'http://localhost:4002/graphql' },
359 { name: 'comments', url: 'http://localhost:4003/graphql' },
360 ],
361 }),
362 });
363
364 const server = new ApolloServer({ gateway });
365 ```
366
3675. **Test Federated Query**
368 ```graphql
369 query FederatedQuery {
370 user(id: "123") {
371 id
372 name
373 posts {
374 id
375 title
376 comments {
377 content
378 author {
379 name # Resolves back to users subgraph
380 }
381 }
382 }
383 }
384 }
385 ```
386
387**Success Criteria:**
388- All subgraphs start without errors
389- Schema composition succeeds
390- Cross-subgraph queries resolve correctly
391- Entity references work bidirectionally
392
393### 4. Real-time Subscriptions
394
395**Goal:** Implement GraphQL subscriptions for real-time updates.
396
397**Steps:**
398
3991. **Configure WebSocket Server**
400 ```typescript
401 // src/server.ts
402 import { createServer } from 'http';
403 import { WebSocketServer } from 'ws';
404 import { useServer } from 'graphql-ws/lib/use/ws';
405 import { ApolloServer } from '@apollo/server';
406 import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
407
408 const httpServer = createServer(app);
409
410 const wsServer = new WebSocketServer({
411 server: httpServer,
412 path: '/graphql',
413 });
414
415 const serverCleanup = useServer(
416 {
417 schema,
418 context: (ctx) => ({
419 user: authenticateWebSocket(ctx.connectionParams),
420 }),
421 },
422 wsServer
423 );
424
425 const server = new ApolloServer({
426 schema,
427 plugins: [
428 ApolloServerPluginDrainHttpServer({ httpServer }),
429 {
430 async serverWillStart() {
431 return {
432 async drainServer() {
433 await serverCleanup.dispose();
434 },
435 };
436 },
437 },
438 ],
439 });
440 ```
441
4422. **Implement PubSub**
443 ```typescript
444 // src/pubsub.ts
445 import { PubSub } from 'graphql-subscriptions';
446 import { RedisPubSub } from 'graphql-redis-subscriptions';
447
448 // For production, use Redis PubSub
449 export const pubsub = new RedisPubSub({
450 connection: process.env.REDIS_URL,
451 });
452
453 // Event types
454 export const EVENTS = {
455 POST_CREATED: 'POST_CREATED',
456 POST_UPDATED: 'POST_UPDATED',
457 COMMENT_ADDED: 'COMMENT_ADDED',
458 USER_ONLINE: 'USER_ONLINE',
459 };
460 ```
461
4623. **Define Subscription Schema**
463 ```graphql
464 type Subscription {
465 postCreated: Post!
466 postUpdated(id: ID!): Post!
467 commentAdded(postId: ID!): Comment!
468 userPresence(roomId: ID!): UserPresenceEvent!
469 }
470
471 type UserPresenceEvent {
472 user: User!
473 status: PresenceStatus!
474 }
475
476 enum PresenceStatus {
477 ONLINE
478 OFFLINE
479 AWAY
480 }
481 ```
482
4834. **Implement Subscription Resolvers**
484 ```typescript
485 // src/resolvers/subscription.resolver.ts
486 import { withFilter } from 'graphql-subscriptions';
487 import { pubsub, EVENTS } from '../pubsub';
488
489 export const SubscriptionResolvers = {
490 Subscription: {
491 postCreated: {
492 subscribe: () => pubsub.asyncIterator([EVENTS.POST_CREATED]),
493 },
494
495 postUpdated: {
496 subscribe: withFilter(
497 () => pubsub.asyncIterator([EVENTS.POST_UPDATED]),
498 (payload, variables) => {
499 return payload.postUpdated.id === variables.id;
500 }
501 ),
502 },
503
504 commentAdded: {
505 subscribe: withFilter(
506 () => pubsub.asyncIterator([EVENTS.COMMENT_ADDED]),
507 (payload, variables, context) => {
508 // Only notify if user has access to the post
509 return payload.commentAdded.postId === variables.postId;
510 }
511 ),
512 },
513 },
514 };
515 ```
516
5175. **Publish Events**
518 ```typescript
519 // src/resolvers/mutation.resolver.ts
520 export const MutationResolvers = {
521 Mutation: {
522 createPost: async (_, { input }, { user, prisma }) => {
523 const post = await prisma.post.create({
524 data: { ...input, authorId: user.id },
525 });
526
527 // Publish to subscribers
528 await pubsub.publish(EVENTS.POST_CREATED, { postCreated: post });
529
530 return { post };
531 },
532
533 addComment: async (_, { input }, { user, prisma }) => {
534 const comment = await prisma.comment.create({
535 data: { ...input, authorId: user.id },
536 });
537
538 // Publish to subscribers watching this post
539 await pubsub.publish(EVENTS.COMMENT_ADDED, { commentAdded: comment });
540
541 return { comment };
542 },
543 },
544 };
545 ```
546
547**Success Criteria:**
548- WebSocket connection established
549- Subscriptions receive real-time updates
550- Filtering works correctly
551- Connection cleanup on disconnect
552- Production-ready with Redis PubSub
553
554## Python Tools
555
556### schema_analyzer.py
557
558**Purpose:** Analyze GraphQL schemas for quality, complexity, and best practices.
559
560**Usage:**
561```bash
562# Basic analysis
563python scripts/schema_analyzer.py schema.graphql
564
565# JSON output for tooling
566python scripts/schema_analyzer.py schema.graphql --output json
567
568# Validate against best practices
569python scripts/schema_analyzer.py schema.graphql --validate
570
571# Analyze complexity and depth
572python scripts/schema_analyzer.py schema.graphql --complexity
573```
574
575**Features:**
576- Type system analysis (types, interfaces, unions, enums)
577- Query/mutation/subscription inventory
578- Complexity scoring per operation
579- Deprecation tracking
580- Naming convention validation
581- Description coverage checking
582- Circular reference detection
583
584### resolver_generator.py
585
586**Purpose:** Generate TypeScript resolvers from GraphQL schema.
587
588**Usage:**
589```bash
590# Generate resolvers
591python scripts/resolver_generator.py schema.graphql --output src/resolvers
592
593# With DataLoader integration
594python scripts/resolver_generator.py schema.graphql --output src/resolvers --dataloader
595
596# For specific types only
597python scripts/resolver_generator.py schema.graphql --output src/resolvers --types User,Post
598
599# Generate with tests
600python scripts/resolver_generator.py schema.graphql --output src/resolvers --tests
601```
602
603**Generated Output:**
604- Resolver files per type
605- Type definitions
606- DataLoader factories
607- Context type definitions
608- Jest test stubs
609
610### federation_scaffolder.py
611
612**Purpose:** Scaffold Apollo Federation subgraphs with proper entity definitions.
613
614**Usage:**
615```bash
616# Create new subgraph
617python scripts/federation_scaffolder.py users-service --entities User,Profile
618
619# With entity references
620python scripts/federation_scaffolder.py posts-service --entities Post --references User
621
622# Full service with Docker
623python scripts/federation_scaffolder.py comments-service --entities Comment --docker --port 4003
624
625# Scaffold gateway
626python scripts/federation_scaffolder.py gateway --subgraphs users:4001,posts:4002,comments:4003
627```
628
629**Generated Structure:**
630```
631service-name/
632├── src/
633│ ├── schema.graphql # Federation schema
634│ ├── resolvers/ # Type resolvers
635│ ├── dataloaders/ # DataLoader factories
636│ ├── datasources/ # Data access layer
637│ └── index.ts # Apollo Server setup
638├── tests/ # Jest tests
639├── Dockerfile # Container definition
640├── docker-compose.yml # Local development
641└── package.json
642```
643
644## Best Practices
645
646### Schema Design
647- Use descriptive names (avoid abbreviations)
648- Document all types and fields
649- Implement Relay-style pagination for lists
650- Use input types for mutations
651- Return payload types from mutations (not raw types)
652- Version breaking changes with new fields (not removal)
653
654### Resolver Patterns
655- Keep resolvers thin (delegate to services)
656- Use DataLoader for all batch-able relations
657- Implement proper error handling
658- Add authentication at resolver level
659- Log slow resolvers for optimization
660
661### Federation
662- Define clear subgraph boundaries
663- Minimize cross-subgraph queries
664- Use `@requires` sparingly
665- Implement proper health checks
666- Version subgraph schemas independently
667
668### Performance
669- Implement query complexity limits
670- Use persisted queries in production
671- Cache with appropriate TTLs
672- Monitor resolver execution time
673- Implement query depth limiting
674
675## References
676
677### Reference Files
678- `references/schema-patterns.md` - Schema design patterns and conventions
679- `references/federation-guide.md` - Apollo Federation architecture guide
680- `references/performance-optimization.md` - GraphQL performance best practices
681
682### External Resources
683- [GraphQL Specification](https://spec.graphql.org/)
684- [Apollo Server Documentation](https://www.apollographql.com/docs/apollo-server/)
685- [Apollo Federation](https://www.apollographql.com/docs/federation/)
686- [DataLoader](https://github.com/graphql/dataloader)
687
688---
689
690**Version:** 1.0.0
691**Last Updated:** 2025-12-16
692**Skill Type:** Engineering specialist
693**Python Tools:** 3 (schema_analyzer.py, resolver_generator.py, federation_scaffolder.py)