Communitygithub.com

GraphQL Yoga v5 模式指南

GraphQL Yoga v5 伺服器、Envelop 插件、訂閱及錯誤遮罩

GraphQL Yoga v5 模式指南 是什麼?

這是一個專為開發者設計的 AI 代理技能,詳細說明了使用 GraphQL Yoga v5 建立高效、可擴展的 GraphQL 伺服器的最佳實踐與模式。技能重點介紹了使用 `createYoga` 和 `createSchema` 進行 schema-first 開發、透過 Envelop 插件系統進行功能擴展、以及利用 SSE (Server-Sent Events) 作為預設的訂閱傳輸方式。此外,它還深入涵蓋了生產環境必備的錯誤遮罩機制、CORS/CSRF 安全設定、檔案上傳實作,並提供了跨執行環境 (Node.js, Bun, Deno, Cloudflare Workers) 的部署指引。核心價值在於其提供的決策框架,能幫助開發者在不同場景下 (如 schema 策略、訂閱傳輸選擇、插件選取) 做出最佳技術決策,並明確指出常見的錯誤陷阱,例如必須從 `graphql` 而非 `graphql-yoga` 匯入 `GraphQLError`,以及在 v5 中不能將原始 `typeDefs`/`resolvers` 直接傳給 `createYoga`。

相容平台✓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 執行)

(你必須使用 createSchema 從 'graphql-yoga' 進行 schema-first 開發 —— 在 v5 中,不支援將原始的 typeDefs/resolvers 物件直接傳遞給 createYoga)

(你必須為所有數值使用具名常數 —— 逾時、TTL、連接埠號碼、限制)

</critical_requirements>


自動偵測: GraphQL Yoga, graphql-yoga, createYoga, createSchema, createPubSub, Envelop, useResponseCache, useCSRFPrevention, usePersistedOperations, GraphQL subscriptions SSE, error masking, maskedErrors, graphql-ws, Yoga plugin hooks, onRequest, onParams

使用時機:

  • 建立需要在 Node.js、Bun、Deno 或 Cloudflare Workers 上運行的 GraphQL 伺服器
  • 需要透過 SSE(預設)或 WebSocket 進行訂閱的 API
  • 使用 Envelop 插件(快取、認證、日誌)擴展 GraphQL 執行
  • 使用 GraphQL Multipart Request 規範進行檔案上傳
  • 需要錯誤遮罩、CORS 和 CSRF 保護的正式環境 API

不應使用時機:

  • 僅使用 REST 且無 GraphQL 需求的 API
  • 簡單的 CRUD 操作,框架的內建路由處理器就足夠
  • 需要聯邦網關時(請考慮專用的網關解決方案)

涵蓋的主要模式:

  • 使用 createYoga 和 createSchema 進行伺服器設定(schema-first)
  • 在 createYoga<ServerContext> 上使用泛型進行型別安全的 context
  • 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 套件中拋出 —— 這些錯誤會繞過遮罩,並將其訊息和 extensions 完整地送達客戶端。


核心模式

模式 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 以了解完整的設定、跨執行環境部署和型別安全 context。


模式 2:型別安全 Context

為 createYoga 傳遞泛型以進行伺服器特定的 context 型別化。context 工廠接收 YogaInitialContext(包含 request 和 params)並回傳你的自訂 context。

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 };
  },
});

優點: 泛型型別流向解析器的 context 參數,request 使用標準的 Fetch API(而非框架特定的 req/res)

請參閱 examples/core.md 以了解完整的 context 模式。


模式 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 等效插件無法做到),插件組合時不會發生衝突

請參閱 examples/plugins.md 以了解自訂插件、生命週期鉤子和所有 Yoga 特定插件。


模式 4:使用 SSE 進行訂閱(預設)

Yoga 預設使用 Server-Sent Events 進行訂閱 —— 不需要 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 函式庫

請參閱 examples/subscriptions.md 以了解 PubSub、WebSocket 設定和過濾。


模式 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 },
});

優點: 非預期的錯誤永遠不會洩漏內部資訊(資料庫細節、堆疊追蹤),有意的錯誤會連同訊息和 extensions 一起通過

請參閱 examples/error-handling.md 以了解自訂遮罩、停用遮罩和開發模式。


模式 6:檔案上傳

Yoga 支援 GraphQL Multipart Request 規範。加入 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();
        // Process file content
        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 特定
Response Cache@graphql-yoga/plugin-response-cache跳過快取查詢的執行
Persisted Operations@graphql-yoga/plugin-persisted-operations在 HTTP 層拒絕未知操作
Defer/Stream@graphql-yoga/plugin-defer-stream透過 HTTP chunked encoding 串流
CSRF Prevention@graphql-yoga/plugin-csrf-prevention在解析前要求自訂標頭
GraphQL SSE@graphql-yoga/plugin-graphql-sse單一連線 SSE 模式

</decision_framework>


<red_flags>

紅旗警示

高優先級:

  • 從 graphql-yoga 而非 graphql 匯入 GraphQLError —— 錯誤的套件,將會失敗
  • 將 typeDefs/resolvers 物件直接傳遞給 createYoga 而未使用 createSchema —— v5 不支援此用法
  • 當存在 Yoga 特定等效插件時,使用了 Envelop 插件 —— 會錯過 HTTP 層級的最佳化(Yoga 回應快取完全跳過解析;Envelop 等效插件無法做到)
  • 在解析器中拋出純 Error 期望客戶端看到訊息 —— 在生產環境中會被遮罩為 "Unexpected error."

中優先級:

  • 未為生產環境設定 CORS 來源 —— 預設為 *,應加以限制
  • 在多個伺服器實例間使用記憶體 PubSub —— 事件不會傳播(請使用 Redis 支援的 createRedisEventTarget)
  • 缺少 graphql 對等相依套件 —— graphql-yoga 需要 graphql 作為對等相依套件,請同時安裝兩者
  • 呼叫 createSchema 但未提供任何 schema —— Yoga 需要一個 schema;它不會推斷出一個

陷阱與邊緣案例:

  • YogaInitialContext.request 是 Fetch API 的 Request,而非 Node.js 的 IncomingMessage —— 請使用 request.headers.get(),而不是 req.headers
  • v5 中的插件執行順序已更改——透過 onPluginInit 中的 addPlugin 新增的插件現在會緊接在新增的插件之後執行,而不是最後
  • useResponseCache 的 session 回呼必須為 PRIVATE 作用域回傳一個字串(使用者 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 會停用 ALL 遮罩,包括堆疊追蹤 —— 請改用自訂 maskError 函數進行選擇性暴露
  • 根據 Fetch 規範,瀏覽器會拒絕使用 credentials: true 搭配 origin: '*'——請指定準確的來源

</red_flags>


<critical_reminders>

關鍵提醒

所有程式碼必須遵循 CLAUDE.md 中的專案慣例

(你必須從 'graphql' 匯入 GraphQLError,而不是從 'graphql-yoga' —— 這是標準的 graphql-js 匯出)

(你必須優先使用 Yoga 特定插件而非 Envelop 等效插件 —— Yoga 插件在 HTTP 層運作,可以為快取/持久化的結果完全跳過 GraphQL 執行)

(你必須使用 createSchema 從 'graphql-yoga' 進行 schema-first 開發 —— 在 v5 中,不支援將原始的 typeDefs/resolvers 物件直接傳遞給 createYoga)

(你必須為所有數值使用具名常數 —— 逾時、TTL、連接埠號碼、限制)

未能遵循這些規則將會導致導入錯誤或執行階段問題。

請在提示時參閱 reference.md 以獲取完整的決策框架和生產環境檢查清單。

</critical_reminders>

Individual skills in this repo

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

相關技能