GraphQL Yoga 模式
快速指南: 使用
createYoga+createSchema创建一个兼容 Fetch API 的 GraphQL 服务器,该服务器可在任何 JS 运行时上运行。Yoga v5 使用 Envelop 进行插件组合,默认使用 SSE 进行订阅,内置错误屏蔽和 CORS。从graphql(而非graphql-yoga)导入GraphQLError以处理有意暴露给客户端的错误。对于 HTTP 级别的优化,优先使用 Yoga 特定插件而非 Envelop 等效插件。
<critical_requirements>
关键:使用此技能前必须注意
所有代码必须遵循 CLAUDE.md 中的项目约定(kebab-case、具名导出、导入顺序、
import type、具名常量)
(你必须从 'graphql' 导入 GraphQLError,而不是从 'graphql-yoga'——它是标准的 graphql-js 导出)
(你必须优先使用 Yoga 特定插件而非 Envelop 等效插件——Yoga 插件在 HTTP 层运行,可以为缓存/持久化的结果完全跳过 GraphQL 执行)
(你必须使用 'graphql-yoga' 中的 createSchema 进行 schema-first 设计——在 v5 中,不支持直接将原始 typeDefs/resolvers 对象传递给 createYoga)
(你必须为所有数值使用具名常量——超时时间、TTL、端口号、限制)
</critical_requirements>
自动检测: GraphQL Yoga, graphql-yoga, createYoga, createSchema, createPubSub, Envelop, useResponseCache, useCSRFPrevention, usePersistedOperations, GraphQL 订阅 SSE, 错误屏蔽, maskedErrors, graphql-ws, Yoga 插件钩子, onRequest, onParams
何时使用:
- 构建需要在 Node.js、Bun、Deno 或 Cloudflare Workers 上运行的 GraphQL 服务器
- 需要通过 SSE(默认)或 WebSocket 进行订阅的 API
- 使用 Envelop 插件扩展 GraphQL 执行(缓存、认证、日志记录)
- 使用 GraphQL 多部分请求规范进行文件上传
- 需要错误屏蔽、CORS 和 CSRF 保护的生产级 API
何时不应使用:
- 纯 REST API,无需 GraphQL
- 简单的 CRUD,使用框架内置路由处理器即可
- 当你需要联邦网关时(请考虑专用网关解决方案)
涵盖的关键模式:
- 使用
createYoga和createSchema(schema-first)进行服务器设置 - 使用
createYoga<ServerContext>的泛型实现类型安全上下文 - Envelop 插件系统:生命周期钩子、自定义插件、Yoga 特定插件
- 订阅:SSE(默认)、通过
graphql-ws的 WebSocket、内置 PubSub - 错误屏蔽和有意暴露的
GraphQLError - 使用 WHATWG
File标量进行文件上传 - 生产加固:CORS、CSRF 预防、GraphQL Armor、日志记录
- 跨运行时部署:Node.js、Bun、Deno、Cloudflare Workers
详细资源:
- examples/core.md - 服务器设置、schema、上下文、解析器、跨运行时部署
- examples/plugins.md - Envelop 插件、自定义插件、生命周期钩子
- examples/subscriptions.md - SSE、WebSocket、PubSub、过滤
- examples/error-handling.md - 错误屏蔽、GraphQLError、自定义屏蔽
- examples/production.md - CORS、CSRF、响应缓存、持久化操作、日志记录
- reference.md - 决策框架、插件参考、生产检查清单
理念
GraphQL Yoga 是一个即开即用、兼容 Fetch API 的 GraphQL 服务器。它的核心构建于 WHATWG Fetch API(Request/Response)之上,使其与运行时无关——相同的服务器代码可以部署到 Node.js、Bun、Deno 和边缘运行时。Envelop 插件系统在 HTTP 和 GraphQL 执行层提供可组合的中间件。
Schema 方法: Yoga 与 schema 库无关。使用 createSchema(schema-first SDL)、Pothos(code-first)或原生的 graphql-js——任何能产生 GraphQLSchema 的方案都可以。
插件优先级: 当同一功能(缓存、持久化操作、defer/stream)同时存在 Envelop 插件和 Yoga 特定插件时,始终选择 Yoga 变体。Yoga 插件钩入 HTTP 层,并能在 GraphQL 执行开始前短路,完全跳过缓存或持久化结果的解析和验证。
错误理念: 在生产环境中,所有意外错误默认会被屏蔽。有意抛出的错误应以 GraphQLError 的形式从 graphql 包中抛出——这些错误会绕过屏蔽,其消息和扩展会完整地到达客户端。
核心模式
模式 1:使用 createYoga 设置服务器
使用 createSchema 创建基于 SDL 的 schema 的 Yoga 实例。yoga 实例本身就是一个 Fetch API 处理器——直接将其传递给任何运行时的 HTTP 服务器。
import { createYoga, createSchema } from "graphql-yoga";
import { createServer } from "node:http";
const PORT = 4000;
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
type Query {
greeting(name: String!): String!
}
`,
resolvers: {
Query: {
greeting: (_, { name }) => `Hello, ${name}!`,
},
},
}),
});
const server = createServer(yoga);
server.listen(PORT, () => {
console.info(`Server running on http://localhost:${PORT}/graphql`);
});
为何良好: createSchema 封装了 makeExecutableSchema,yoga 实例是标准的 Fetch 处理器,可在任何运行时上工作
完整设置、跨运行时部署和类型安全上下文请参见 examples/core.md。
模式 2:类型安全上下文
为 createYoga 传递泛型以指定服务器特定的上下文类型。context 工厂接收 YogaInitialContext(包含 request 和 params)并返回你的自定义上下文。
import { createYoga, type YogaInitialContext } from "graphql-yoga";
interface ServerContext {
req: IncomingMessage;
res: ServerResponse;
}
const yoga = createYoga<ServerContext>({
schema,
context: async ({ request }: YogaInitialContext) => {
const token = request.headers.get("authorization");
return { user: token ? await verifyToken(token) : null };
},
});
为何良好: 泛型类型会流向解析器的上下文参数,request 使用标准的 Fetch API(而非框架特定的 req/res)
完整上下文模式请参见 examples/core.md。
模式 3:Envelop 插件系统
插件通过 plugins 数组传递。当可用时,优先使用 Yoga 特定插件——它们在 HTTP 层运行,可以完全跳过 GraphQL 执行。
import { createYoga } from "graphql-yoga";
import { useResponseCache } from "@graphql-yoga/plugin-response-cache";
const CACHE_TTL_MS = 2_000;
const yoga = createYoga({
schema,
plugins: [
useResponseCache({
session: () => null,
ttl: CACHE_TTL_MS,
}),
],
});
为何良好: Yoga 响应缓存会跳过缓存结果的解析/验证(Envelop 等效插件无法做到),插件可以无冲突地组合
自定义插件、生命周期钩子和所有 Yoga 特定插件请参见 examples/plugins.md。
模式 4:使用 SSE(默认)的订阅
Yoga 默认使用服务器发送事件进行订阅——无需 WebSocket 设置。在订阅解析器中使用 AsyncGenerator 语法。
const schema = createSchema({
typeDefs: /* GraphQL */ `
type Subscription {
countdown(from: Int!): Int!
}
`,
resolvers: {
Subscription: {
countdown: {
subscribe: async function* (_, { from }) {
for (let i = from; i >= 0; i--) {
await new Promise((resolve) => setTimeout(resolve, 1_000));
yield { countdown: i };
}
},
},
},
},
});
为何良好: 无需 WebSocket 基础设施,可通过 HTTP 代理和负载均衡器工作,客户端使用 graphql-sse 库
PubSub、WebSocket 设置和过滤请参见 examples/subscriptions.md。
模式 5:错误屏蔽和 GraphQLError
Yoga 默认屏蔽所有意外错误。对于有意暴露给客户端的错误,抛出 GraphQLError(来自 graphql)——这些错误会绕过屏蔽。
import { GraphQLError } from "graphql";
const NOT_FOUND_CODE = "USER_NOT_FOUND";
throw new GraphQLError("User not found", {
extensions: { code: NOT_FOUND_CODE },
});
为何良好: 意外错误永远不会泄露内部信息(数据库详情、堆栈跟踪),有意错误会连同消息和扩展一起通过
自定义屏蔽、禁用屏蔽和开发模式请参见 examples/error-handling.md。
模式 6:文件上传
Yoga 支持 GraphQL 多部分请求规范。添加 File 标量,并在解析器中接收 WHATWG File 对象。
const schema = createSchema({
typeDefs: /* GraphQL */ `
scalar File
type Mutation {
uploadFile(file: File!): Boolean!
}
`,
resolvers: {
Mutation: {
uploadFile: async (_, { file }: { file: File }) => {
const content = await file.arrayBuffer();
// 处理文件内容
return true;
},
},
},
});
为何良好: 使用标准的 WHATWG File API(与浏览器相同),无需额外包,使用 multipart: false 禁用
完整的文件上传模式请参见 examples/core.md。
<decision_framework>
决策框架
Schema 方法
需要根据 SDL 自动生成类型?
+-- 是 --> createSchema(schema-first,使用 typeDefs + resolvers)
+-- 否 --> 需要在 schema 定义中获得完整的 TypeScript 推断?
+-- 是 --> Code-first 库(例如 Pothos)——将生成的 GraphQLSchema 传递给 Yoga
+-- 否 --> 原生 graphql-js GraphQLSchema
订阅传输
需要订阅?
+-- 是 --> 客户端是否需要双向通信?
| +-- 是 --> 通过 graphql-ws 的 WebSocket(添加 ws + graphql-ws 包)
| +-- 否 --> SSE(默认,零配置,可通过代理工作)
+-- 否 --> 无需设置订阅
插件选择
该功能是否有 Yoga 特定插件?
+-- 是 --> 使用 Yoga 插件(HTTP 级别钩子,可以跳过执行)
+-- 否 --> 使用 Envelop 插件(GraphQL 执行级别钩子)
Yoga 特定插件(优先于 Envelop)
| 插件 | 包 | 为何使用 Yoga 特定版本 |
|---|---|---|
| 响应缓存 | @graphql-yoga/plugin-response-cache | 跳过缓存查询的执行 |
| 持久化操作 | @graphql-yoga/plugin-persisted-operations | 在 HTTP 层拒绝未知操作 |
| Defer/Stream | @graphql-yoga/plugin-defer-stream | 通过 HTTP 分块编码进行流式传输 |
| CSRF 预防 | @graphql-yoga/plugin-csrf-prevention | 在解析之前要求自定义头部 |
| GraphQL SSE | @graphql-yoga/plugin-graphql-sse | 单连接 SSE 模式 |
</decision_framework>
<red_flags>
危险信号
高优先级:
- 从
graphql-yoga而非graphql导入GraphQLError——错误的包,会失败 - 不使用
createSchema而直接将typeDefs/resolvers对象传递给createYoga——v5 不支持 - 在存在 Yoga 特定等效插件时使用 Envelop 插件——会错过 HTTP 级别的优化(Yoga 响应缓存会完全跳过解析;Envelop 等效插件无法做到)
- 在解析器中抛出普通
Error期望客户端看到消息——在生产环境中会被屏蔽为“意外错误。”
中优先级:
- 未在生产环境中配置 CORS 源——默认值为
*,应加以限制 - 在多个服务器实例间使用内存 PubSub——事件不会传播(请使用 Redis 支持的
createRedisEventTarget) - 缺少
graphql对等依赖——graphql-yoga需要graphql作为对等依赖,需同时安装 - 在没有 schema 的情况下调用
createSchema——Yoga 需要一个 schema;它不会推断一个
陷阱与边缘情况:
YogaInitialContext.request是 Fetch API 的Request,不是 Node.js 的IncomingMessage——使用request.headers.get(),而不是req.headers- v5 中插件执行顺序已更改——通过
onPluginInit中的addPlugin添加的插件现在会紧跟在添加它的插件之后执行,而不是最后 useResponseCache的session回调必须为私有范围返回一个字符串(用户 ID)或为公共范围返回null——返回undefined会破坏缓存- SSE 订阅通过 HTTP(text/event-stream)——某些代理可能会缓冲事件;对于 Nginx 设置
X-Accel-Buffering: no - 上传中的
File标量会给你一个 WHATWGFile对象——使用.text()、.arrayBuffer()或.stream()方法(而不是直接的 Node.jsBuffer) - Yoga 内置的 GraphiQL 默认启用——在生产环境中使用
graphiql: false禁用 - 将
maskedErrors设置为false会禁用包括堆栈跟踪在内的所有屏蔽——请改用自定义maskError函数进行选择性暴露 - 根据 Fetch 规范,浏览器会拒绝
credentials: true与origin: '*'的组合——请指定确切的源
</red_flags>
<critical_reminders>
关键提醒
所有代码必须遵循 CLAUDE.md 中的项目约定
(你必须从 'graphql' 导入 GraphQLError,而不是从 'graphql-yoga'——它是标准的 graphql-js 导出)
(你必须优先使用 Yoga 特定插件而非 Envelop 等效插件——Yoga 插件在 HTTP 层运行,可以为缓存/持久化的结果完全跳过 GraphQL 执行)
(你必须使用 'graphql-yoga' 中的 createSchema 进行 schema-first 设计——在 v5 中,不支持直接将原始 typeDefs/resolvers 对象传递给 createYoga)
(你必须为所有数值使用具名常量——超时时间、TTL、端口号、限制)
不遵循这些规则将导致导入失败或在运行时出现意外行为。
</critical_reminders>