<!-- title: Claude Code + GraphQL全链路实战 -->

# Claude Code + GraphQL全链路实战：Schema设计到实时订阅推送

## 概述

本教程面向有一定后端开发经验的工程师，手把手教你使用 Claude Code 从零构建一个完整的 GraphQL API 服务——涵盖 Schema 设计、Resolver 实现、DataLoader 性能优化、JWT 认证、WebSocket 实时订阅，以及 GraphQL Code Generator 类型安全生成。学完后你将掌握用 Claude Code 高效交付生产级 GraphQL 服务的全流程。

**适用人群**：有 Node.js/TypeScript 基础，想系统学习 GraphQL 开发的工程师
**学习目标**：独立完成一个带认证、订阅、类型安全的生产级 GraphQL API

## 前置条件

| 工具 | 版本要求 | 说明 |
|------|---------|------|
| Node.js | ≥ 20.0.0 | Apollo Server 5 强制要求 |
| Claude Code CLI | 最新版 | `npm install -g @anthropic-ai/claude-code` |
| PostgreSQL | ≥ 15 | 或使用 Docker 快速启动 |
| Docker | 最新版 | 用于数据库和 Redis |
| 基础知识 | TypeScript、REST API 概念 | GraphQL 经验非必需 |

---

## 第一步：项目初始化与 Apollo Skills 安装

### 1.1 创建项目骨架

在终端中执行：

```bash
mkdir graphql-blog-api && cd graphql-blog-api
npm init -y
```

然后用 Claude Code 启动开发：

```bash
claude
```

在 Claude Code 中输入：

```
初始化一个 TypeScript + Apollo Server 5 的 GraphQL 项目。
使用 pnpm 作为包管理器，配置 tsconfig.json 支持ESM模块。
项目结构按 src/schema、src/resolvers、src/models、src/utils 分层。
```

Claude Code 将自动生成项目结构、安装依赖、配置 TypeScript。预期输出如下：

```
src/
├── schema/         # GraphQL Schema 定义
├── resolvers/      # Resolver 实现
├── models/         # 数据模型（Prisma）
├── utils/          # 工具函数（auth、dataloader）
├── context.ts      # GraphQL Context 定义
└── index.ts        # 入口文件
```

### 1.2 安装 Apollo GraphQL Skills

Apollo 官方提供了 11 个 Claude Code Skills，覆盖 GraphQL 开发全链路。安装方式：

```bash
# 方式一：通过 Skills CLI
npx skills add apollographql/skills

# 方式二：通过 Claude Code Plugin
# 在 Claude Code 中执行：
/plugin marketplace add apollographql/skills
/plugin install apollo-skills@apollo-marketplace
```

安装后可用的 Skills 命令：

| 命令 | 用途 |
|------|------|
| `/apollo-skills:apollo-server` | Apollo Server 配置与最佳实践 |
| `/apollo-skills:graphql-schema` | Schema 设计规范与模式 |
| `/apollo-skills:graphql-operations` | Query/Mutation/Fragment 编写 |
| `/apollo-skills:apollo-connectors` | REST API 集成为 GraphQL |
| `/apollo-skills:apollo-client` | Apollo Client 前端集成 |

**常见问题**：若 `npx skills add` 报网络错误，改用 `gh skill install apollographql/skills --agent claude-code`。

---

## 第二步：Schema 设计——Schema-First 方法论

### 2.1 确定数据模型

在 Claude Code 中输入：

```
设计一个博客系统的 GraphQL Schema，包含以下实体：
- User: id, email, name, avatar, posts, createdAt
- Post: id, title, content, status(DRAFT/PUBLISHED/ARCHIVED), author, comments, tags, createdAt, updatedAt
- Comment: id, content, author, post, createdAt
- Tag: id, name, posts

支持的 Query：
- user(id), users(pagination, filter), me
- post(id), posts(pagination, filter)
- searchPosts(query)

支持的 Mutation：
- createPost, updatePost, deletePost
- createComment, updateComment
- login, register, updateProfile

支持的 Subscription：
- onPostPublished
- onCommentAdded(postId)

使用 Cursor-based 分页（Relay-style connections）
```

### 2.2 生成 Schema 文件

Claude Code 会在 `src/schema/` 下生成结构化的 `.graphql` 文件：

```graphql
# src/schema/types.graphql
scalar DateTime
scalar Cursor

enum PostStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}

interface Node {
  id: ID!
}

type User implements Node {
  id: ID!
  email: String!
  name: String
  avatar: String
  posts(first: Int, after: Cursor): PostConnection!
  createdAt: DateTime!
}

type Post implements Node {
  id: ID!
  title: String!
  content: String!
  status: PostStatus!
  author: User!
  comments(first: Int, after: Cursor): CommentConnection!
  tags: [Tag!]!
  createdAt: DateTime!
  updatedAt: DateTime!
}

type Comment implements Node {
  id: ID!
  content: String!
  author: User!
  post: Post!
  createdAt: DateTime!
}

type Tag implements Node {
  id: ID!
  name: String!
  posts(first: Int, after: Cursor): PostConnection!
}
```

```graphql
# src/schema/connections.graphql
type PostConnection {
  edges: [PostEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type PostEdge {
  node: Post!
  cursor: Cursor!
}

type CommentConnection {
  edges: [CommentEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type CommentEdge {
  node: Comment!
  cursor: Cursor!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: Cursor
  endCursor: Cursor
}
```

### 2.3 Schema 设计决策

在 Claude Code 中调用 Apollo Schema Skill 获取设计指导：

```
/apollo-skills:graphql-schema
```

Skill 会提供以下关键指导原则：

1. **命名规范**：Mutation 使用 `动词+名词`（`createPost`、`updatePost`），Query 使用名词（`user`、`posts`）
2. **分页选择**：Cursor-based 分页在数据量大时性能更稳定，Offset-based 适合固定页码场景
3. **错误处理**：使用 `UserFacingError` 接口统一错误格式，而非 HTTP 状态码
4. **Null 约定**：字段默认 nullable，只有 `ID!` 和明确必填字段标记 `!`

---

## 第三步：Prisma 数据层 + Resolver 实现

### 3.1 Prisma Schema 配置

在 Claude Code 中输入：

```
配置 Prisma 连接 PostgreSQL，定义与 GraphQL Schema 对应的数据模型。
使用 docker-compose.yml 启动 PostgreSQL。
支持软删除、自动时间戳、全文搜索索引。
```

Claude Code 生成 `prisma/schema.prisma`：

```prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  avatar    String?
  password  String
  posts     Post[]
  comments  Comment[]
  createdAt DateTime @default(now()) @map("created_at")

  @@map("users")
}

model Post {
  id        String      @id @default(cuid())
  title     String
  content   String
  status    PostStatus  @default(DRAFT)
  authorId  String      @map("author_id")
  author    User        @relation(fields: [authorId], references: [id])
  comments  Comment[]
  tags      PostTag[]
  createdAt DateTime    @default(now()) @map("created_at")
  updatedAt DateTime    @updatedAt @map("updated_at")

  @@index([status])
  @@index([authorId])
  @@map("posts")
}

model Comment {
  id        String   @id @default(cuid())
  content   String
  authorId  String   @map("author_id")
  postId    String   @map("post_id")
  author    User     @relation(fields: [authorId], references: [id])
  post      Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
  createdAt DateTime @default(now()) @map("created_at")

  @@map("comments")
}

model Tag {
  id    String    @id @default(cuid())
  name  String    @unique
  posts PostTag[]

  @@map("tags")
}

model PostTag {
  postId String @map("post_id")
  tagId  String @map("tag_id")
  post   Post   @relation(fields: [postId], references: [id], onDelete: Cascade)
  tag    Tag    @relation(fields: [tagId], references: [id], onDelete: Cascade)

  @@id([postId, tagId])
  @@map("post_tags")
}
```

以及 `docker-compose.yml`：

```yaml
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: graphql_blog
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: devpass
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  pgdata:
```

启动服务：

```bash
docker compose up -d
npx prisma migrate dev --name init
npx prisma generate
```

### 3.2 Resolver 实现

在 Claude Code 中输入：

```
实现所有 GraphQL Resolver，包含：
1. Query resolvers: user, users(带分页), me(需认证), post, posts(带过滤), searchPosts
2. Mutation resolvers: register, login(返回JWT), createPost, updatePost, deletePost, createComment
3. Field resolvers: User.posts, Post.author, Post.comments, Post.tags
4. 分页使用 cursor-based，基于 Prisma cursor API
```

Claude Code 生成的核心 Resolver 示例（`src/resolvers/post.resolver.ts`）：

```typescript
import { Resolvers } from '../generated/graphql';
import { GraphQLError } from 'graphql';

export const postResolvers: Resolvers = {
  Query: {
    posts: async (_, { first = 10, after, filter }, { prisma }) => {
      const where = filter ? {
        status: filter.status || undefined,
        authorId: filter.authorId || undefined,
      } : {};

      const posts = await prisma.post.findMany({
        where,
        take: first + 1,
        cursor: after ? { id: after } : undefined,
        orderBy: { createdAt: 'desc' },
        include: { author: true, tags: { include: { tag: true } } },
      });

      const hasNextPage = posts.length > first;
      const edges = hasNextPage ? posts.slice(0, -1) : posts;

      return {
        edges: edges.map(post => ({
          node: post,
          cursor: post.id,
        })),
        pageInfo: {
          hasNextPage,
          hasPreviousPage: !!after,
          startCursor: edges[0]?.id,
          endCursor: edges[edges.length - 1]?.id,
        },
        totalCount: await prisma.post.count({ where }),
      };
    },

    searchPosts: async (_, { query }, { prisma }) => {
      return prisma.post.findMany({
        where: {
          status: 'PUBLISHED',
          OR: [
            { title: { contains: query, mode: 'insensitive' } },
            { content: { contains: query, mode: 'insensitive' } },
          ],
        },
        take: 20,
        orderBy: { createdAt: 'desc' },
      });
    },
  },

  Mutation: {
    createPost: async (_, { input }, { prisma, userId }) => {
      if (!userId) {
        throw new GraphQLError('Unauthorized', {
          extensions: { code: 'UNAUTHENTICATED' },
        });
      }

      const { tagIds, ...postData } = input;

      return prisma.post.create({
        data: {
          ...postData,
          authorId: userId,
          tags: tagIds
            ? { create: tagIds.map(tagId => ({ tagId })) }
            : undefined,
        },
        include: { author: true, tags: { include: { tag: true } } },
      });
    },
  },
};
```

**常见问题**：

| 问题 | 原因 | 解决 |
|------|------|------|
| `Cannot read property 'id' of null` | 关联字段未 include | Resolver 中显式 include 所需关联 |
| Cursor 分页跳过第一条 | take/skip 计算错误 | 使用 `take: first + 1` 方案判断 hasNextPage |
| N+1 查询性能差 | Field resolver 逐条查询 | 第四步使用 DataLoader 解决 |

---

## 第四步：DataLoader 解决 N+1 查询

### 4.1 问题本质

当查询 `posts { edges { node { author { name } } } }` 时，如果返回 20 篇文章，每篇文章的 `author` 字段 resolver 会单独查询一次数据库——总共 1 + 20 = 21 次查询。这就是经典的 N+1 问题。

### 4.2 在 Claude Code 中实现 DataLoader

```
为 User、Post、Tag 创建 DataLoader，支持批量加载。
将 DataLoader 实例挂载到 GraphQL Context。
确保同一请求内缓存有效，请求间不缓存。
```

Claude Code 生成 `src/utils/dataloaders.ts`：

```typescript
import DataLoader from 'dataloader';
import { PrismaClient } from '@prisma/client';

export function createLoaders(prisma: PrismaClient) {
  return {
    userLoader: new DataLoader<string, any>(async (ids) => {
      const users = await prisma.user.findMany({
        where: { id: { in: ids as string[] } },
      });
      const map = new Map(users.map(u => [u.id, u]));
      return ids.map(id => map.get(id) ?? null);
    }),

    postLoader: new DataLoader<string, any>(async (ids) => {
      const posts = await prisma.post.findMany({
        where: { id: { in: ids as string[] } },
        include: { author: true },
      });
      const map = new Map(posts.map(p => [p.id, p]));
      return ids.map(id => map.get(id) ?? null);
    }),

    postsByAuthorLoader: new DataLoader<string, any[]>(async (authorIds) => {
      const posts = await prisma.post.findMany({
        where: { authorId: { in: authorIds as string[] } },
      });
      const map = new Map<string, any[]>();
      for (const post of posts) {
        const list = map.get(post.authorId) ?? [];
        list.push(post);
        map.set(post.authorId, list);
      }
      return authorIds.map(id => map.get(id) ?? []);
    }),
  };
}
```

然后在 Context 中注入：

```typescript
// src/context.ts
import { PrismaClient } from '@prisma/client';
import { createLoaders } from './utils/dataloaders';

const prisma = new PrismaClient();

export interface GraphQLContext {
  prisma: PrismaClient;
  userId?: string;
  loaders: ReturnType<typeof createLoaders>;
}

export function createContext({ req }: any): GraphQLContext {
  const authHeader = req?.headers?.authorization || '';
  const token = authHeader.replace('Bearer ', '');
  const userId = verifyJWT(token); // 自行实现

  return {
    prisma,
    userId: userId || undefined,
    loaders: createLoaders(prisma),
  };
}
```

**效果对比**：

```
# 无 DataLoader: 查询 20 篇文章的作者
SQL Queries: 1 (posts) + 20 (authors) = 21 次

# 有 DataLoader: 查询 20 篇文章的作者
SQL Queries: 1 (posts) + 1 (batch authors WHERE id IN (...)) = 2 次
```

---

## 第五步：JWT 认证与权限控制

### 5.1 认证中间件

在 Claude Code 中输入：

```
实现 JWT 认证：
1. register mutation: 邮箱密码注册，bcrypt 加密
2. login mutation: 返回 JWT token
3. Context 中解析 token 获取 userId
4. 未认证访问受保护资源时抛出 UNAUTHENTICATED 错误
5. 使用 @auth directive 标记需要认证的字段
```

核心认证 Resolver：

```typescript
// src/resolvers/auth.resolver.ts
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { GraphQLError } from 'graphql';

const JWT_SECRET = process.env.JWT_SECRET || 'change-me-in-production';

export const authResolvers = {
  Mutation: {
    register: async (_, { input }, { prisma }) => {
      const existing = await prisma.user.findUnique({
        where: { email: input.email },
      });
      if (existing) {
        throw new GraphQLError('Email already registered', {
          extensions: { code: 'CONFLICT' },
        });
      }

      const password = await bcrypt.hash(input.password, 12);
      const user = await prisma.user.create({
        data: { email: input.email, name: input.name, password },
      });

      const token = jwt.sign({ userId: user.id }, JWT_SECRET, {
        expiresIn: '7d',
      });

      return { token, user };
    },

    login: async (_, { email, password }, { prisma }) => {
      const user = await prisma.user.findUnique({ where: { email } });
      if (!user || !(await bcrypt.compare(password, user.password))) {
        throw new GraphQLError('Invalid credentials', {
          extensions: { code: 'UNAUTHENTICATED' },
        });
      }

      const token = jwt.sign({ userId: user.id }, JWT_SECRET, {
        expiresIn: '7d',
      });

      return { token, user };
    },
  },
};
```

### 5.2 权限守卫

在 Context 中提取用户信息后，Resolver 内部通过 `userId` 判断权限：

```typescript
deletePost: async (_, { id }, { prisma, userId }) => {
  if (!userId) throw unauthorized();
  
  const post = await prisma.post.findUnique({ where: { id } });
  if (!post) throw notFound('Post');
  if (post.authorId !== userId) throw forbidden('Not your post');
  
  return prisma.post.delete({ where: { id } });
},
```

---

## 第六步：GraphQL Subscriptions 实时推送

### 6.1 WebSocket 架构

实时推送是 GraphQL 相对 REST 的核心优势之一。通过 WebSocket 长连接，客户端订阅特定事件，服务端在事件触发时主动推送。

```mermaid
sequenceDiagram
    participant Client as 客户端
    participant Server as Apollo Server
    participant PubSub as Redis PubSub
    participant Resolver as Resolver

    Client->>Server: WebSocket 连接 + subscription onPostPublished
    Server-->>Client: subscription 确认 (ws://)
    
    Note over Client,Server: 另一个客户端操作...
    
    Client->>Server: mutation createPost
    Server->>Resolver: createPost resolver
    Resolver->>PubSub: publish("POST_PUBLISHED", payload)
    PubSub->>Server: 消息广播
    Server->>Client: subscription 触发: onPostPublished { ... }
```

### 6.2 在 Claude Code 中实现

```
实现两个 GraphQL Subscription：
1. onPostPublished - 新文章发布时通知
2. onCommentAdded(postId) - 特定文章收到新评论时通知

使用 Redis PubSub 作为消息后端（支持多实例部署）。
配置 WebSocket 握手时验证 JWT token。
```

Claude Code 生成 `src/utils/pubsub.ts`：

```typescript
import { Redis } from 'ioredis';
import { PubSubAsyncIterator } from './pubsub-async-iterator';

const PUBSUB_PREFIX = 'graphql:';

export class RedisPubSub {
  private publisher: Redis;
  private subscriber: Redis;

  constructor(redisUrl: string) {
    this.publisher = new Redis(redisUrl);
    this.subscriber = new Redis(redisUrl);
  }

  async publish(trigger: string, payload: any): Promise<void> {
    await this.publisher.publish(
      `${PUBSUB_PREFIX}${trigger}`,
      JSON.stringify(payload)
    );
  }

  asyncIterator(trigger: string): PubSubAsyncIterator {
    return new PubSubAsyncIterator(this.subscriber, `${PUBSUB_PREFIX}${trigger}`);
  }
}
```

Subscription Resolver（`src/resolvers/subscription.resolver.ts`）：

```typescript
import { Resolvers } from '../generated/graphql';

export const subscriptionResolvers: Resolvers = {
  Subscription: {
    onPostPublished: {
      subscribe: (_, __, { pubsub }) => pubsub.asyncIterator('POST_PUBLISHED'),
      resolve: (payload) => payload,
    },
    onCommentAdded: {
      subscribe: (_, { postId }, { pubsub }) =>
        pubsub.asyncIterator(`COMMENT_ADDED_${postId}`),
      resolve: (payload) => payload,
    },
  },
};
```

在 Mutation 中触发订阅：

```typescript
// 在 createPost resolver 中
createPost: async (_, { input }, { prisma, userId, pubsub }) => {
  // ...创建文章逻辑...
  
  if (post.status === 'PUBLISHED') {
    await pubsub.publish('POST_PUBLISHED', post);
  }
  return post;
},

// 在 createComment resolver 中
createComment: async (_, { input }, { prisma, userId, pubsub }) => {
  // ...创建评论逻辑...
  
  await pubsub.publish(`COMMENT_ADDED_${input.postId}`, comment);
  return comment;
},
```

### 6.3 Apollo Server WebSocket 配置

```typescript
// src/index.ts
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { makeExecutableSchema } from '@graphql-tools/schema';

const app = express();
const httpServer = createServer(app);

// WebSocket 服务器
const wsServer = new WebSocketServer({
  server: httpServer,
  path: '/graphql',
});

useServer(
  {
    schema,
    context: async (ctx) => {
      // 从 WebSocket 连接参数中提取 token
      const token = ctx.connectionParams?.authorization?.replace('Bearer ', '');
      const userId = token ? verifyJWT(token) : null;
      return { prisma, userId, loaders: createLoaders(prisma), pubsub };
    },
  },
  wsServer
);

// Apollo Server
const server = new ApolloServer({ schema });
await server.start();
app.use('/graphql', express.json(), expressMiddleware(server, { context }));

httpServer.listen(4000, () => {
  console.log('Server ready at http://localhost:4000/graphql');
  console.log('Subscriptions ready at ws://localhost:4000/graphql');
});
```

**常见问题**：

| 问题 | 原因 | 解决 |
|------|------|------|
| WebSocket 连接立即断开 | JWT 验证失败 | 检查 `connectionParams` 传递方式 |
| 订阅不触发 | PubSub 实例不一致 | 确保 Resolver 和 Subscription 共享同一 PubSub |
| 内存泄漏 | 未取消订阅 | 客户端组件卸载时调用 `unsubscribe()` |

---

## 第七步：GraphQL Code Generator 类型安全

### 7.1 配置 Codegen

```
配置 GraphQL Code Generator：
1. 从 schema.graphql 生成 TypeScript 类型
2. 生成类型安全的 Resolver 签名
3. 在 package.json 中添加 codegen 脚本
4. 配置 watch 模式用于开发
```

Claude Code 生成 `codegen.ts`：

```typescript
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: './src/schema/**/*.graphql',
  generates: {
    './src/generated/graphql.ts': {
      plugins: [
        'typescript',
        'typescript-resolvers',
        'typescript-operations',
      ],
      config: {
        contextType: '../context#GraphQLContext',
        useIndexSignature: true,
        enumValues: {
          PostStatus: '@prisma/client#PostStatus',
        },
        mappers: {
          User: '@prisma/client#User as UserModel',
          Post: '@prisma/client#Post as PostModel',
          Comment: '@prisma/client#Comment as CommentModel',
        },
      },
    },
  },
};

export default config;
```

在 `package.json` 中添加脚本：

```json
{
  "scripts": {
    "codegen": "graphql-codegen --config codegen.ts",
    "codegen:watch": "graphql-codegen --config codegen.ts --watch",
    "dev": "concurrently \"npm run codegen:watch\" \"tsx watch src/index.ts\""
  }
}
```

### 7.2 类型安全的好处

Codegen 生成的类型确保 Resolver 签名与 Schema 完全一致：

```typescript
// src/generated/graphql.ts（自动生成）
export type Resolvers<TContext = GraphQLContext> = {
  Query: {
    posts: Resolver<Promise<PostConnection>, {}, TContext,
      { first?: number; after?: string; filter?: PostFilterInput }>;
    post: Resolver<Promise<Post | null>, {}, TContext, { id: string }>;
    me: Resolver<Promise<User | null>, {}, TContext>;
  };
  Mutation: {
    createPost: Resolver<Promise<Post>, {}, TContext,
      { input: CreatePostInput }>;
    // ...
  };
};
```

编写 Resolver 时，TypeScript 会自动检查参数类型、返回类型是否匹配 Schema：

```typescript
// ✅ 正确：返回类型匹配
posts: async (_, { first = 10, after, filter }, { prisma }) => {
  // TypeScript 知道 first 是 number，filter 是 PostFilterInput | undefined
  // ...
},

// ❌ 编译错误：返回类型不匹配
posts: async () => {
  return { wrong: 'field' }; // Error: Type '{ wrong: string }' is not assignable to 'PostConnection'
},
```

---

## 完整实战：用 Claude Code 一键构建博客 API

将以上所有步骤串联，在 Claude Code 中使用一条完整的 Prompt：

```
构建一个生产级 GraphQL 博客 API，要求：

1. 技术栈：Apollo Server 5 + Prisma + PostgreSQL + TypeScript + Redis
2. 数据模型：User、Post、Comment、Tag
3. 功能：
   - Cursor-based 分页的查询
   - JWT 认证（register/login）
   - 权限控制（只能编辑自己的文章）
   - DataLoader 解决 N+1 查询
   - GraphQL Subscriptions（新文章/评论通知）
   - GraphQL Code Generator 类型安全
4. 部署：Docker Compose 一键启动
5. 测试：Vitest 单元测试 + 集成测试

输出完整的项目代码，包含所有文件。
```

### 启动与验证

```bash
# 启动基础设施
docker compose up -d

# 数据库迁移
npx prisma migrate dev

# 生成类型
npm run codegen

# 启动开发服务器
npm run dev

# 验证：打开 Apollo Sandbox
# 浏览器访问 http://localhost:4000/graphql
```

在 Apollo Sandbox 中测试：

```graphql
# 注册用户
mutation Register {
  register(input: {
    email: "test@example.com"
    name: "Test User"
    password: "password123"
  }) {
    token
    user { id email name }
  }
}

# 创建文章
mutation CreatePost($token: String!) {
  createPost(input: {
    title: "My First Post"
    content: "Hello GraphQL!"
    status: PUBLISHED
  }) @auth(token: $token) {
    id title status createdAt
    author { name }
  }
}

# 查询文章列表（带分页）
query Posts {
  posts(first: 10) {
    edges {
      node { id title createdAt }
      cursor
    }
    pageInfo { hasNextPage endCursor }
    totalCount
  }
}

# 订阅新文章
subscription OnPostPublished {
  onPostPublished {
    id title
    author { name }
  }
}
```

### 项目完整工作流程

```mermaid
graph TB
    subgraph 开发环境
        A[Schema .graphql 文件] -->|graphql-codegen| B[TypeScript 类型]
        B --> C[类型安全 Resolver]
        C --> D[Apollo Server]
        D --> E[HTTP: Query/Mutation]
        D --> F[WebSocket: Subscription]
    end

    subgraph 数据层
        C -->|Prisma Client| G[PostgreSQL]
        C -->|DataLoader| G
    end

    subgraph 实时推送
        C -->|publish| H[Redis PubSub]
        H -->|asyncIterator| F
    end

    subgraph Claude Code 工作流
        I[Claude Code CLI] -->|生成 Schema| A
        I -->|生成 Resolver| C
        I -->|生成 DataLoader| C
        I -->|运行 codegen| B
        I -->|生成测试| J[Vitest 测试]
    end

    style I fill:#f9a825,stroke:#333
    style D fill:#42b883,stroke:#333
    style G fill:#336791,stroke:#333
    style H fill:#dc382d,stroke:#333
```

---

## Claude Code 加速 GraphQL 开发的高效模式

### Prompt 模板一：Schema 迭代

```
当前的 GraphQL Schema 如下：
[粘贴 schema 内容]

需要新增以下功能：
- [描述新需求]

请更新 Schema，确保：
1. 遵循 Relay 风格 connections
2. 添加对应的 Input 类型
3. 保持与现有类型的一致性
4. 更新 codegen 配置
```

### Prompt 模板二：Resolver Debug

```
这个 GraphQL Query 返回了错误：
query { posts(first: 5) { edges { node { author { posts { edges { node { id } } } } } } } }

错误信息：[粘贴错误]

相关的 Resolver 代码：
[粘贴 resolver 代码]

Prisma Schema：
[粘贴 prisma schema]

请诊断 N+1 问题并提供修复方案。
```

### Prompt 模板三：性能优化

```
分析以下 GraphQL Resolver 的性能问题：

[粘贴代码]

数据库查询日志显示：
[粘贴日志]

要求：
1. 识别 N+1 查询
2. 添加适当的 DataLoader
3. 优化 Prisma include 策略
4. 添加查询复杂度限制
```

### 高效技巧汇总

| 技巧 | 操作 | 收益 |
|------|------|------|
| Schema First | 先写 `.graphql` 再生成代码 | 前后端契约清晰 |
| Codegen Watch | `npm run codegen:watch` | 保存即生成，零等待 |
| Apollo Sandbox | 浏览器内调试 Query/Mutation | 无需安装客户端 |
| DataLoader 批量化 | 所有外键关联字段使用 Loader | 查询数从 N+1 降到 2 |
| 订阅隔离测试 | 使用 `ws://` CLI 工具单独测试 | 排除 HTTP 层干扰 |

---

## 延伸阅读

- [Apollo GraphQL 官方教程](https://www.apollographql.com/tutorials/) — Apollo 官方从入门到 Federation 的系统课程
- [GraphQL Code Generator 文档](https://the-guild.dev/graphql/codegen) — 类型生成工具的完整配置参考
- [GraphQL 规范 2025 正式版](https://spec.graphql.org/October2025/) — GraphQL 语言规范的最新版本
- [Claude Code Agent SDK 文档](https://code.claude.com/docs/en/agent-sdk/overview) — 将 GraphQL Agent 部署为 API 的官方指南

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| 适合训练营类型 | 全栈工程师训练营、API 开发专项训练营 |
| 建议教学时长 | 6-8 小时（含实战练习） |
| 难度等级 | ⭐⭐⭐（中等，需 TypeScript 基础） |
| 前置课程 | Node.js + TypeScript + 数据库基础 |
| 实验环境要求 | Docker Desktop + Claude Code CLI |
| 学员产出 | 一个可运行的 GraphQL 博客 API + 测试套件 |
| 讲师准备时间 | 约 2 小时（环境验证 + 示例数据准备） |

## 参考来源

1. [Apollo GraphQL Agent Skills (GitHub)](https://github.com/apollographql/skills) — Apollo 官方提供的 11 个 Claude Code Skills，覆盖 Server/Client/Schema/Connectors 全链路
2. [GraphQL Tutorial: Apollo Server in 13 Steps [2026]](https://tech-insider.org/graphql-tutorial-apollo-server-13-steps-2026/) — 2026 年最新 Apollo Server 5 + Prisma + JWT 完整教程，包含 CVE 防御和 Docker 部署
3. [Build a GraphQL API with TypeScript in 2026 (Encore)](https://encore.dev/articles/typescript-graphql-api-guide) — Schema-first vs Code-first 对比分析，Pothos + Yoga 现代方案
4. [Claude Code Event-Driven Agent System (GitHub)](https://github.com/hwillGIT/claude-event-driven-agents) — 基于 Redis 的消息队列 Agent 架构，含 Hooks 和多队列支持
5. [Apollo GraphQL Subscriptions 文档](https://www.apollographql.com/docs/apollo-server/data/subscriptions) — WebSocket 实时订阅的官方实现指南

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
