在 Zeit Now 上用 Node.js 部署 Hasura 事件触发器:从 echo 回显到 GraphQL 回写数据库的完整实战指南 在 Zeit Now 上用 Node.js 部署 Hasura 事件触发器从 echo 回显到 GraphQL 回写数据库的完整实战指南【免费下载链接】graphql-engineBlazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.项目地址: https://gitcode.com/gh_mirrors/gr/graphql-engine本篇技术指南围绕 community/boilerplates/event-triggers/zeit-now/nodejs 中的两个 Node.js 样板展开echo回显事件负载与mutation在事件回调中通过 GraphQL 写回数据库。读者将掌握如何在 Zeit Nownow无服务器平台上部署 Hasura Event Trigger 的 webhook 端点理解事件负载payload的完整结构并学会从 Hasura Console 创建触发器、测试事件投递与排查请求/响应。文中所有命令、配置与代码均来自当前仓库可直接复制运行。一、事件触发器的整体工作方式在深入样板之前先建立对 Hasura Event Trigger 的整体认知。根据 event-triggers 样板总述事件触发器是 Hasura GraphQL Engine 提供的一种异步业务逻辑机制对数据库表执行 insert / update / delete 操作时Hasura 会捕获该事件并将包含事件数据的 HTTP 请求webhook发送到你配置的端点。这个端点可以是 AWS Lambda、Google Cloud Functions、Azure Functions、Netlify Functions也可以是我们本文的主角——Zeit Now。从架构图可以看出事件的来源不限于 GraphQL mutations——REST API 调用、后台任务对数据库的写入同样会触发事件而事件的接收端则是无服务器函数或微服务。这正是事件驱动架构在数据库变更 → 外部副作用场景下的标准形态比如发推送通知、ETL 数据转换、写审计日志等。本仓库的样板目录按平台 → 语言 → 用例三层组织Zeit Now 平台下目前提供四个样板详见 zeit-now/README.mdNodeJS EchoNodeJS MutationGo EchoGo Mutation二、环境准备部署 Hasura GraphQL Engine 与安装 now CLI在部署触发器之前需要先准备好两样东西一个运行中的 Hasura GraphQL EngineHGE实例以及 Zeit Now 的 CLI 工具。2.1 部署 Hasura GraphQL Engine按 zeit-now/README.md 的说明最快的方式是通过 Heroku 一键部署按钮使用免费的 Postgres 插件。也可以参考仓库根目录的 docker-compose.yaml 或 install-manifests 以其他方式部署。部署完成后记下 GraphQL Engine 的 URL——在 mutation 示例中我们将把它作为环境变量传入函数。2.2 安装与登录 now CLIZeit Now 平台的命令行工具是now安装与登录步骤如下命令来自 zeit-now/README.md# 1. 在 zeit.co 创建账号 # 2. 全局安装 now CLI npm install -g now # 3. 登录 now login登录完成后即可在任意样板目录中执行部署命令。三、示例一NodeJS Echo —— 回显事件负载Echo 样板的定位是帮助理解事件负载的结构以及如何解析数据见 zeit-now/nodejs 顶层 README。它的逻辑非常简单收到 Hasura 投递的 webhook 请求后把请求中的事件 ID、操作类型、表名、schema、触发器名以及新旧数据原样回显给调用方。3.1 创建表打开 Hasura Console进入Data标签页创建如下表字段定义来自 echo/README.mdTable name: note Columns: id Integer auto-increment note Text Primary key: id这张note表将作为事件触发器监控的数据源——之后我们对它执行 insert/update/deleteHasura 就会把对应事件投递到 webhook。3.2 函数代码解析核心代码位于 echo/index.js完整代码如下const { json, send } require(micro); module.exports async (req, res) { let payload; try { payload await json(req); } catch (error) { send(res, 400, { error }); return; } const { id, event: {op, data}, table, trigger } payload; send(res, 200, { message: received ${id} for ${op} operation on ${table.name} table in ${table.schema} schema from ${trigger.name} trigger, oldData: data.old, newData: data.new, }); };几个值得注意的实现细节函数基于micro框架依赖版本锁定为9.3.3见 echo/package.json。json(req)用于把请求体解析为对象send(res, status, body)用于返回 HTTP 响应。入口函数是async的并且用 try/catch 处理 JSON 解析失败的情况当请求体不是合法 JSON 时返回 400这保证了 webhook 端点对畸形请求的健壮性。通过 ES6 解构一次性取出事件负载的关键字段id事件 ID、event.op操作类型、event.data新旧数据、table表信息、trigger触发器信息这正好展示了事件负载的顶层结构详见第五节。正常路径返回200 状态码这是事件触发器判定事件处理成功的关键从代码结构看返回非 200 会被 Hasura 视为处理失败错误处理的语义详见第六节。3.3 部署到 Zeit Nowecho目录下提供了两份部署配置。首先是 now.json它声明了 Now 平台的版本与构建方式{ project: zeit-echo, version: 2, builds: [{ src: index.js, use: now/node }] }其中use: now/node指明使用 Node.js 构建器index.js即函数入口。其次echo/package.json 声明了运行时依赖与启动方式{ name: zeit-echo, dependencies: { micro: 9.3.3 }, license: MIT, main: ./index.js, scripts: { start: micro } }进入echo目录直接执行部署命令来自 echo/README.mdnow部署完成后控制台会输出一个形如https://zeit-echo-hhasdewasd.now.sh的端点记下它作为NOW_URL。注意Now.sh 的部署是不可变的——每次部署代码都会分配一个新的 URL。如果需要固定地址可以对部署结果设置 alias例如now alias deployment-url alias避免每次更新代码后都要去 Console 里改 webhook 地址。3.4 创建事件触发器回到 Hasura Console进入Events标签页创建一个新的触发器配置来自 echo/README.mdTrigger name: note_trigger Schema/Table: public/note Operations: Insert, Update, Delete Webhook URL: NOW_URL其中NOW_URL替换为刚才记下的部署端点。这样配置的含义是public.note表上的任意 insert、update、delete 操作都会触发一个指向该 webhook 的 HTTP 请求。3.5 测试触发器与负载解析测试方式很简单回到 Console 的Data标签页浏览note表并插入一行数据然后进入Events标签页的note_trigger查看已处理事件的请求体request与响应体response。插入一行数据后Hasura 投递给 webhook 的请求体即事件负载如下来自 echo/README.md{ event: { op: INSERT, data: { old: null, new: { text: new-entry, id: 1 } } }, created_at: 2018-10-01T17:21:03.76895Z, id: b30cc7e6-9f3b-48ee-9a10-16cce333df40, trigger: { name: note_trigger, id: 551bd6a9-6f8b-4644-ba7f-80c08eb9227b }, table: { schema: public, name: note } }注意event.data.old在 INSERT 场景下为null没有旧数据event.data.new则为新插入的行这里实际插入了text列示例中为演示将id一并列出。webhook 端点的响应体即 echo 函数回显的内容为{ message: received b30cc7e6-9f3b-48ee-9a10-16cce333df40 for INSERT operation on note table in public schema from note_trigger trigger, oldData: null, newData: { text: new-entry, id: 1 } }从这条响应可以直观看到id、op、table.name、table.schema、trigger.name等字段被正确解析并拼接进了messagedata.old与data.new被原样透传为oldData与newData。整个数据库变更 → Hasura 投递 → Serverless 函数解析 → 回显链路至此完整跑通。四、示例二NodeJS Mutation —— 在事件回调中写回数据库Mutation 样板的定位更进一步在事件发生时通过 GraphQL mutation 向数据库写入关联数据见 zeit-now/nodejs 顶层 README。典型场景是更新主表数据的同时自动记录一份修订历史——这正是本示例演示的note/note_revision两表联动。4.1 创建两张表按照 mutation/README.md 的定义先创建主表noteTable name: note Columns: id Integer auto-increment note Text Primary key: id再创建修订记录表note_revisionTable name: note_revision Columns: id Integer auto-increment note Text note_id Integer update_at Timestamp, default: now() Primary key: idnote_revision通过note_id关联到note的某一行update_at默认取当前时间。事件触发后webhook 会在note_revision中插入一条新记录从而形成可追溯的编辑历史。4.2 函数代码解析核心代码位于 mutation/index.js完整代码如下const {json, send} require(micro); const { query } require(graphqurl); const HGE_ENDPOINT process.env.HGE_ENDPOINT; const MUTATION_UPDATE_NOTE_REVISION mutation updateNoteRevision ($object: note_revision_insert_input!) { insert_note_revision (objects: [$object]) { affected_rows returning { id } } } ; module.exports async (req, res) { let payload; try { payload await json(req); } catch (error) { send(res, 400, { error }); return; } const { id, event: {op, data}, table, trigger } payload; try { const result await query({ query: MUTATION_UPDATE_NOTE_REVISION, endpoint: HGE_ENDPOINT, variables: { object: { note_id: data.old.id, note: data.new.note } }, }); send(res, 200, { result }); } catch (error) { send(res, 500, { error }); } };与 echo 示例相比这段代码有三个关键差异引入了graphqurl依赖版本要求^0.3.1见 mutation/package.json。graphqurl提供了query({ query, endpoint, variables })这样的轻量 GraphQL 客户端接口用于从函数内部发起 GraphQL 请求。通过环境变量HGE_ENDPOINT注入 GraphQL Engine 的端点代码第 4 行const HGE_ENDPOINT process.env.HGE_ENDPOINT;这使得同一份代码可以部署到任意 HGE 实例而不必硬编码地址。使用 GraphQL mutation 写回数据库定义的updateNoteRevisionmutation 接收一个note_revision_insert_input!类型的对象调用insert_note_revision插入一条修订记录并返回affected_rows与returning { id }。函数在收到事件后把data.old.id作为note_id、把data.new.note作为note组装成变量提交。错误处理上函数区分了两种失败JSON 解析失败返回400GraphQL 写库失败返回500此时 Hasura 会记录本次投递失败详见第六节。4.3 部署通过-e注入环境变量Mutation 样板的部署命令与 echo 不同需要显式传入HGE_ENDPOINT命令来自 mutation/README.mdnow -e HGE_ENDPOINThttps://my-app.herokuapp.com/v1/graphql其中HGE_ENDPOINT就是你的 Hasura GraphQL Engine 端点注意路径是/v1/graphql。部署完成后同样会得到形如https://zeit-mutation-hhasdewasd.now.sh的端点记为NOW_URL。now.json 与 echo 版本结构一致仅项目名不同zeit-mutation{ project: zeit-mutation, version: 2, builds: [{ src: index.js, use: now/node }] }4.4 创建触发器在 Hasura Console 的Events标签页创建触发器配置来自 mutation/README.mdTrigger name: note_revision_trigger Schema/Table: public/note Operations: Update Webhook URL: NOW_URL与 echo 触发器不同这里只勾选了Update操作——因为我们关心的是笔记被修改这个动作并据此记录修订历史。4.5 测试与响应解析测试方式回到Data标签页的note表进入 Browse rows编辑某一行已有数据随后检查note_revision表是否出现了一条新的修订记录并到Events页面的note_revision_trigger中查看请求与响应。当note表第 1 行从note1被更新为note1 updated时Hasura 投递的事件负载如下来自 mutation/README.md{ event: { op: UPDATE, data: { old: { note: note1, id: 1 }, new: { note: note1 updated, id: 1 } } }, created_at: 2018-10-02T06:38:22.67311Z, id: f57a1c79-72ba-4c19-8791-37d1b9616bcf, trigger: { name: note_revision_trigger, id: 5d85cbd1-c134-45ce-810c-7ecd3b4fc1ee }, table: { schema: public, name: note } }与 INSERT 事件不同UPDATE 事件的event.data.old与event.data.new同时非空old是更新前的行new是更新后的行。mutation 函数正是利用这一特性从old取id主键从new取更新后的note文本组装成note_revision的新记录。函数执行 GraphQL mutation 后返回的响应体为{ result: { data: { insert_note_revision: { returning: [ { __typename: note_revision, id: 1 } ], affected_rows: 1, __typename: note_revision_mutation_response } } } }affected_rows: 1表示成功插入一行修订记录returning[0].id是新记录的自增主键。至此更新笔记 → 事件触发 → webhook 自动写入修订历史的闭环完全打通。五、事件负载Payload结构深度拆解综合两个示例中出现的请求体可以归纳出 Hasura Event Trigger 投递给 webhook 的事件负载标准结构。这是编写任何事件处理函数的首要功课——正如 zeit-now/nodejs 顶层 README 所说echo 示例的价值就在于帮助理解事件负载和如何解析数据。顶层字段类型含义idstring (UUID)本次事件的唯一标识可用于日志追踪与幂等处理created_atstring (ISO 8601)事件产生时间如2018-10-01T17:21:03.76895Zevent.opstring操作类型INSERT/UPDATE/DELETEevent.data.oldobject | null变更前的行数据INSERT 时为nullevent.data.newobject | null变更后的行数据DELETE 时为nulltable.schemastring表所在的数据库 schema如publictable.namestring表名如notetrigger.namestring触发器名称如note_triggertrigger.idstring (UUID)触发器的唯一标识三个需要特别注意的点old/new的取值取决于操作类型INSERT 只有newDELETE 只有oldUPDATE 两者都有。事件处理函数必须防御性地处理data.old或data.new为null的情况echo 与 mutation 两个示例都通过解构直接使用实际业务中建议先判空。table与trigger字段携带的是元信息通常用于日志输出、多表复用同一 webhook 时的路由分发一个 webhook 端点可以通过table.name区分来自哪张表的事件。示例中的字段顺序与格式如created_at带时区后缀、UUID 格式与 GraphQL Engine 的实际投递保持一致可以直接作为编写本地测试夹具的参考数据。六、从源码与配置理解事件触发器的设计约束除了样板代码本身当前仓库还提供了理解事件触发器机制的更多材料平台无关的事件触发语义event-triggers 样板总述 明确指出这些函数实现的是由 Hasura GraphQL Engine 在数据库 insert/update/delete 时触发的异步业务逻辑的示例用例并列举了 echo、写回数据库、异步推送通知FCM/APNS、ETL 数据转换如更新 algolia 索引等典型场景。也就是说本仓库的两个示例只是冰山一角同一套 webhook 协议可以承载各类异步副作用。官方文档体系仓库的 event-triggers.md 与 docs/docs/event-triggers/ 目录下还有完整的事件触发器用户文档含 17 个 mdx 页面涵盖了触发器创建、投递重试、日志查看等更深入的运维话题可作为继续深入的手册。从示例代码可推断的协议约定echo 与 mutation 两个函数均以 200 作为成功响应码、以 4xx/5xx 作为失败响应码。结合 Hasura 事件触发器投递失败后重试的通用设计详见官方事件触发器文档事件处理函数应当保证幂等——因为同一事件可能因重试而被投递多次。本仓库的 mutation 示例在重试场景下会重复插入note_revision记录实际生产实现时通常需要结合事件id或业务键做去重。部署不可变性echo/README.md 与 mutation/README.md 都明确提示Now.sh 的每次部署都会生成新 URL需要为部署设置 alias 以获得稳定地址。这一约束意味着webhook URL 一旦变化需要同步更新 Hasura Console 中触发器配置的 Webhook URL。七、常见问题与排查建议结合两个样板的部署与测试流程整理出以下实操排查清单现象可能原因排查方向部署后访问端点 404now.json的builds.src与实际入口文件名不一致或未在函数所在目录执行now核对index.js位置与 now.json 中的srcnow命令未找到CLI 未安装或未登录重新执行npm install -g now与now loginConsole 中事件状态显示失败webhook 返回了非 200 状态码查看事件详情中的 request/response body对照 echo 示例修正函数返回mutation 示例提示 GraphQL 请求失败HGE_ENDPOINT环境变量缺失、端点路径错误或 HGE 未开启对应表的 insert 权限确认部署命令包含-e HGE_ENDPOINThttps://.../v1/graphql检查 Console 中note_revision表的角色权限更新了代码但 webhook 仍是旧逻辑Now 部署不可变每次部署产生新 URL为最新部署设置 alias 并在触发器中使用该稳定 URL八、延伸在更多平台上复用同一套样板逻辑事件处理函数的核心逻辑解析 payload、调用 GraphQL与平台无关。如果你希望把同样的 echo / mutation 用例部署到其他无服务器平台仓库中提供了对应实现可供对照AWS LambdaNode.js/Python/Ruby/Go/JavaGoogle Cloud FunctionsNode.js/PythonAzure FunctionsNode.jsNetlify FunctionsNode.js这些平台的样板共享同一套事件负载协议与webhook 返回 200 即成功的约定迁移时只需替换平台的函数入口与部署配置即可。若需新增其他平台或语言的支持仓库欢迎以 PR 或 issue标记help-wanted的形式贡献。结语通过本文你已经完整走通了部署 HGE → 安装 now CLI → 部署 echo 与 mutation 两个 Node.js 样板 → 创建触发器 → 验证事件投递与响应的全流程。echo 示例让你吃透事件负载的结构mutation 示例则展示了如何在事件回调中通过 GraphQL 与数据库交互——这两项能力是构建一切基于数据库事件的无服务器业务逻辑通知、审计、ETL、数据回写的基础。继续深入可阅读 event-triggers.md 与 docs/docs/event-triggers/ 中的官方文档掌握投递重试、日志与故障排查等生产级细节。【免费下载链接】graphql-engineBlazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.项目地址: https://gitcode.com/gh_mirrors/gr/graphql-engine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考