Communitygithub.com

agents-inc/skills

GraphQL Yoga v5 服务器、Envelop 插件、订阅、错误屏蔽

skills 是什么?

此技能提供了一整套使用 GraphQL Yoga v5 构建生产级 GraphQL 服务器的模式和实践。它基于 Fetch API,使其能在 Node.js、Bun、Deno 和 Cloudflare Workers 等任何 JavaScript 运行时上运行。核心技术包括:使用 `createYoga` 和 `createSchema` 进行服务器设置、使用 Envelop 插件系统进行 HTTP 和 GraphQL 执行层的中间件组合、默认使用服务器发送事件 (SSE) 进行订阅(可选 WebSocket)、内置错误屏蔽机制(其中使用 `GraphQLError` 显示预期错误),以及文件上传、CORS 和 CSRF 保护等开箱即用的功能。该技能强调了更喜欢 Yoga 特定插件(如响应缓存或持久化操作)而非通用 Envelop 插件的重要性,因为 Yoga 插件可以在 HTTP 层短路执行,从而显著提升性能。它适用于需要订阅、缓存、认证、日志记录以及边缘运行时部署的 API。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agents-inc/skills/tree/HEAD/src/skills/api-graphql-yoga

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

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

详细资源:


理念

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 标量会给你一个 WHATWG File 对象——使用 .text()、.arrayBuffer() 或 .stream() 方法(而不是直接的 Node.js Buffer)
  • 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>

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

相关技能