- 后端
- API设计
【免费下载链接】graphql-yoga
🧘 Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance & great developer experience. The core of Yoga implements WHATWG Fetch API and can run/deploy on any JS environment.
本篇以 graphql-yoga 仓库中 examples/envelop/graphql-socket.io 示例为核心,讲解如何在不使用 Yoga HTTP 入口的情况下,把@envelop/core的插件化执行管线(parse/validate/execute/subscribe + 插件体系)接入一个既有的 Socket.IO 服务器,从而获得 GraphQL 的实时/长连接执行能力。读完本文,你能完整复现该示例的运行流程,并理解 Envelop 的envelop()工厂如何为每个操作(operation)按需产出执行函数,以及useSchema、useLogger两个插件在管线中的具体挂载点。
示例定位与运行方式
该示例的主题是:用 Envelop 实现 GraphQL 流程,并借助@n1ru4l/socket-io-graphql-server库为现有 Socket.IO 服务器增强 GraphQL 能力。它属于examples/envelop/目录下一组“Envelop 跨传输层集成”示例之一(同目录还有 apollo-server、graphql-ws、graphql-sse、graphql-socket.io 等),专门演示 Envelop 作为执行编排核心、传输协议可插拔的设计。
README 给出的运行步骤完整如下,可直接照做:
- 在仓库根目录安装全部依赖(使用
pnpm); cd进入examples/envelop/graphql-socket.io目录,运行pnpm run start;- 运行
pnpm run test:client,对服务器执行一次测试操作。
对照 package.json 中的 scripts 定义,这两条命令的实际含义是:
"scripts": { "start": "ts-node index.ts", // 直接以 ts-node 启动服务端 "test:client": "ts-node test-client.ts" // 直接以 ts-node 启动客户端 }即无需任何编译步骤,ts-node直接解释执行 TypeScript 源文件。服务端依赖中的关键版本(以当前仓库为准):socket.io4.8.3、socket.io-client4.8.3、@n1ru4l/socket-io-graphql-server与@n1ru4l/socket-io-graphql-client均为 0.13.0、graphql17.0.2、@graphql-tools/schema10.0.31;其中@envelop/core使用"*"表示走本仓库 workspace 内源码(package.json 中engines要求 Node>=18.0.0,运行本示例的最低环境即由此确定)。
服务端实现逐段解析
完整的服务器代码见 index.ts,共 55 行,可以分成四个环节:
import * as http from 'http'; import { execute, parse, subscribe, validate } from 'graphql'; import { Server } from 'socket.io'; import { envelop, useLogger, useSchema } from '@envelop/core'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { registerSocketIOGraphQLServer } from '@n1ru4l/socket-io-graphql-server'; // 1. 定义可执行 schema const schema = makeExecutableSchema({ typeDefs: /* GraphQL */ ` type Query { hello: String! } `, resolvers: { Query: { hello: () => 'World', }, }, }); // 2. 创建 Envelop 执行管线 const getEnveloped = envelop({ parse, validate, execute, subscribe, plugins: [useSchema(schema), useLogger()], }); // 3. 创建 HTTP + Socket.IO 服务器 const httpServer = http.createServer(); const socketServer = new Server(httpServer); // 4. 将 GraphQL 能力注册到 Socket.IO 服务器 registerSocketIOGraphQLServer({ socketServer, getParameter: async ({ socket, graphQLPayload }) => { const { schema, contextFactory, parse, validate, execute, subscribe } = getEnveloped({ socket, graphQLPayload, }); return { parse, validate, execute, subscribe, graphQLExecutionParameter: { schema, contextValue: await contextFactory(), }, }; }, }); httpServer.listen(3000, () => { // eslint-disable-next-line no-console console.log('Listening on http://localhost:3000'); });schema 与 Envelop 管线的组装
schema 由@graphql-tools/schema的makeExecutableSchema构造,包含一个最简Query.hello字段。envelop()工厂接收parse/validate/execute/subscribe四个 graphql-js 基础函数(作为管线默认实现)以及plugins数组,返回一个getEnveloped函数。
从源码看(create.ts),envelop()内部先创建 orchestrator 对插件做编排,随后返回的getEnveloped(context)在每次调用时以传入的初始 context 为种子,产出一组被 instrumentation 包裹的执行函数:
return { parse: instrumented.fn(instrumentation?.parse, typedOrchestrator.parse(context)), validate: instrumented.fn(instrumentation?.validate, typedOrchestrator.validate(context)), contextFactory: instrumented.fn(instrumentation?.context, typedOrchestrator.contextFactory(context as any)), execute: instrumented.asyncFn(instrumentation?.execute, typedOrchestrator.execute), subscribe: instrumented.asyncFn(instrumentation?.subscribe, typedOrchestrator.subscribe), schema: typedOrchestrator.getCurrentSchema(), };这解释了示例中getEnveloped({ socket, graphQLPayload })的写法:每次收到一个 GraphQL 操作,都调用一次getEnveloped并带上操作特有的信息(Socket.IO 的socket对象与原始graphQLPayload)作为初始 context,得到该操作私有的parse/validate/execute/subscribe与schema。context 的初始化即从这里开始——这正是把“连接级信息”(如 socket)带入执行上下文的入口点。
两个插件的挂载点
plugins: [useSchema(schema), useLogger()]分别做两件事,均可在 packages/envelop/core/src/plugins 下核对实现:
useSchema(schema)(use-schema.ts):在onPluginInit钩子中调用setSchema(schema),把 schema 注入编排器,使getEnveloped()返回的schema字段(orchestrator 的getCurrentSchema())以及后续解析、校验都能拿到同一份 schema。useLogger()(use-logger.ts):挂在onExecute/onSubscribe钩子上,执行时输出execute-start/execute-end,订阅时输出subscribe-start/subscribe-end(含args与结果result)。它支持skipIntrospection选项,为 true 时在onParse阶段检测 introspection 操作并跳过日志。因此运行test:client时,服务端终端会打印出本次查询的执行出入参——这也是验证“操作确实走过了 Envelop 管线”的直观证据。
getParameter:Envelop 与 Socket.IO 协议的交接点
registerSocketIOGraphQLServer是外部库@n1ru4l/socket-io-graphql-server提供的注册函数,其核心入参是getParameter回调:每当 Socket.IO 客户端发起一次 GraphQL 操作,该回调就会收到{ socket, graphQLPayload },并需要返回:
parse/validate/execute/subscribe:由getEnveloped(...)产出的、经过全部插件增强的执行函数;graphQLExecutionParameter:包含schema与contextValue: await contextFactory()——注意contextFactory()是异步函数,这里await其结果得到完整的执行上下文值。
从示例结构可以看出 Envelop 的通用集成模式:传输层(Socket.IO)只负责协议的收发与生命周期,Envelop 负责把一次操作转化为“解析 → 校验 → 执行/订阅”的可编排管线;getParameter就是二者的交接点,任何能在该回调中拿到请求/操作信息的传输层,都可以按同样方式接入。
客户端测试脚本
pnpm run test:client执行的 test-client.ts 完整代码如下:
import { io } from 'socket.io-client'; import { createSocketIOGraphQLClient } from '@n1ru4l/socket-io-graphql-client'; const socket = io('http://localhost:3000'); const client = createSocketIOGraphQLClient(socket); socket.on('connect', async () => { const execution = client.execute({ operation: /* GraphQL */ ` query { hello } `, }); for await (const result of execution) { // eslint-disable-next-line no-console console.log(JSON.stringify(result)); } client.destroy(); socket.close(); });其流程为:用socket.io-client连接http://localhost:3000(即服务端httpServer.listen(3000)的端口)→ 用createSocketIOGraphQLClient(socket)把该 socket 包装为 GraphQL 客户端 → 在connect事件中调用client.execute({ operation })发起query { hello }查询 →client.execute返回一个可异步迭代的执行流,用for await...of逐条消费结果(该写法同样兼容订阅类操作的多帧结果)→ 结束后client.destroy()与socket.close()释放连接。成功运行后,客户端终端会打印服务端 schema 返回的查询结果 JSON,服务端终端则会打印useLogger的execute-start/execute-end日志。
关键要点小结
- 本示例证明 Envelop(
@envelop/core)不绑定任何特定 HTTP 框架或传输层:示例目录examples/envelop/下并列的 apollo-server、graphql-ws、graphql-sse、graphql-socket.io 等示例,展示了同一套envelop()+ 插件机制接入不同传输协议的方式; envelop()返回的getEnveloped是“按操作实例化”的执行函数工厂(见 create.ts),在 Socket.IO 场景中通过getParameter回调把 socket 级信息注入初始 context;useSchema通过onPluginInit注入 schema,useLogger通过onExecute/onSubscribe提供执行可观测性,两者都是插件钩子体系的直接体现;- 运行前提:仓库根目录
pnpm install、Node >= 18、按 README 顺序执行pnpm run start与pnpm run test:client;示例包为private: true,仅用于学习参考,不作为 npm 包发布。
- 后端
- API设计
【免费下载链接】graphql-yoga
🧘 Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance & great developer experience. The core of Yoga implements WHATWG Fetch API and can run/deploy on any JS environment.
相关推荐
Next.js Pages Router 集成 Socket.IO 实战:基于自定义 Server 的实时通信示例
Next.js Pages Router 集成 Socket.IO 实战:基于自定义 Server 的实时通信示例 本文基于 socket.io 仓库中的 ne
后端即时通讯WebSocket基于 Prisma 与 graphql-yoga 实现 GraphQL 权限控制:从数据模型到 Resolver 实战
基于 Prisma 与 graphql yoga 实现 GraphQL 权限控制:从数据模型到 Resolver 实战 本文是一篇面向 GraphQL 服务端开
后端数据库GraphQL基于 Next.js 与 Nhost 实现认证与实时 GraphQL 的完整示例(with-nhost-auth-realtime-graphql)实战指南
基于 Next.js 与 Nhost 实现认证与实时 GraphQL 的完整示例(with nhost auth realtime graphql)实战指南 导
前端后端Web框架SSR前端构建
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考