Keystone 6 Hooks 实战指南在 CRUD GraphQL 操作中嵌入自定义业务逻辑【免费下载链接】keystoneThe superpowered headless CMS for Node.js — built with GraphQL and React项目地址: https://gitcode.com/gh_mirrors/key/keystoneKeystone 6 为每个 list 自动生成完整的 CRUD GraphQL API而hooks机制允许你在这套核心操作的生命周期中注入自定义业务逻辑。本指南以官方文档docs/content/docs/guides/hooks.md为骨架结合packages/core源码实现系统讲解如何用 hooks 改写入参、校验数据、触发副作用并深入对比 list hooks 与 field hooks 的使用场景。什么是 HookHook 是定义在 schema 配置 中的函数在 GraphQL 操作执行时被触发。Keystone 支持四种核心 hookresolveInput、validate、beforeOperation和afterOperation分别覆盖数据解析、校验、写入前、写入后四个阶段。Hook 支持async函数除了resolveInput需要返回值外其余 hook 无需返回值在批量操作如createMany时每个被操作的数据项都会各自触发一次 hook。先看一个最基础的示例每次创建新用户时在控制台打印日志。import { config, list } from keystone-6/core; import { text } from keystone-6/core/fields; export default config({ lists: { User: list({ fields: { name: text(), email: text(), }, hooks: { afterOperation: ({ operation, item }) { if (operation create) { console.log(New user created. Name: ${item.name}, Email: ${item.email}); } } }, }), }, });这个函数会在 GraphQL API 执行create、update或deletemutation 时触发。由于afterOperation支持create/update/delete三种操作我们通过检查operation参数判断当前操作类型再用item参数读取新创建用户的值。用 resolveInput 改写入库数据当执行create或update操作时你可能希望在数据写入数据库前做预处理。例如保证博客文章的title字段首字母大写。resolveInputhook 允许我们拿到 GraphQL mutation 传入的数据并修改后再保存。import { config, list } from keystone-6/core; import { text } from keystone-6/core/fields; export default config({ lists: { Post: list({ fields: { title: text({ validation: { isRequired: true } }), content: text({ validation: { isRequired: true } }), }, hooks: { resolveInput: ({ resolvedData }) { const { title } resolvedData; if (title) { return { ...resolvedData, // Ensure the first letter of the title is capitalised title: title[0].toUpperCase() title.slice(1) } } // We always return resolvedData from the resolveInput hook return resolvedData; } }, }), }, });注意resolveInputhook 必须始终返回修改后的resolvedData即使你没有做任何修改。resolveInput在 create/update 时被调用resolvedData中的值是经过字段类型输入解析器处理后的结果。例如password字段会把明文转换成加密哈希。如果只想查看原始输入请使用inputData参数。在 update 操作中还可以通过item参数访问数据库中当前存储的值。所有 hook 都会收到context参数提供完整的 context API 访问能力。理解 resolvedData 的解析阶段从源码看resolveInput是 数据解析过程 的最终阶段。在packages/core/src/lib/core/mutations/index.ts中getResolvedData函数按以下顺序处理 create/update 数据初始化将resolvedData设置为 GraphQL mutation 的data输入值默认值内置仅 createresolvedData中为undefined且配置了默认值的字段被设为默认值关系字段内置关系字段的值被转换为 Prisma 嵌套写对象nested write objects嵌套 create 操作在此阶段执行并返回 ID所有connect、set、disconnect的项都会校验存在性to-many 关系返回{ connect, set, disconnect }对象to-one 关系返回{ connect }或{ disconnect: true }字段值内置某些字段类型将 GraphQL 输入转换为数据库所需的不同类型或格式如password的哈希转换见packages/core/src/fields/types/password/index.ts字段 hooks用户定义resolveInput字段 hook 可为单个字段返回新值列表 hooks用户定义resolveInput列表 hook 可为整个resolvedData对象返回新值字段级 hook 和列表级 hook 的resolveInput都在访问控制access control之后执行这保证了只有通过权限校验的数据才会进入你的业务逻辑。用 validate 校验输入数据在将解析后的数据写入数据库前常常需要根据业务规则校验。例如空字符串在 GraphQL 中是合法的String值但业务上可能不允许博客标题为空。validatehook 可以拒绝这类输入import { config, list } from keystone-6/core; import { text } from keystone-6/core/fields; export default config({ lists: { Post: list({ fields: { title: text({ validation: { isRequired: true } }), content: text({ validation: { isRequired: true } }), }, hooks: { validate: ({ resolvedData, addValidationError }) { const { title } resolvedData; if (title ) { addValidationError(The title of a blog post cannot be the empty string); } } }, }), }, });validatehook 接收的是默认值和resolveInputhook 完成之后的resolvedData。通过addValidationError函数上报错误信息可以多次调用来收集不同问题。Keystone 会中止操作并将这些错误消息转换为 GraphQL 错误返回给调用方。validatehook 还接收operation、inputData、item和context参数可用于更复杂的检查。从源码packages/core/src/lib/core/hooks.ts可见字段级校验 hooks 会并行执行Promise.all错误消息会以${list.listKey}.${fieldKey}: ${msg}的格式聚合列表级校验 hook 的错误则以${list.listKey}: ${msg}格式聚合最终抛出validationFailureError在 GraphQL API 层表现为ValidationFailureError。重要提醒不要把数据校验validation和访问控制access control混淆。若想判断用户是否被允许执行某操作应配置 访问控制规则而不是在validate里实现权限逻辑。用 beforeOperation / afterOperation 触发副作用系统数据变更时你可能需要触发外部副作用例如用户首次创建账号后发送欢迎邮件import { config, list } from keystone-6/core; import { text } from keystone-6/core/fields; // Keystone leaves it up to you to decide how best to implement email in your system import { sendWelcomeEmail } from ./lib/welcomeEmail; export default config({ lists: { User: list({ fields: { name: text(), email: text(), }, hooks: { afterOperation: ({ operation, item }) { if (operation create) { sendWelcomeEmail(item.name, item.email); } } }, }), }, });beforeOperation与afterOperation很相似但用途不同beforeOperation的item参数包含操作执行前数据库中已存储的数据create 时为undefinedafterOperation的item表示数据库中更新后的新数据更新前的原数据通过originalItem提供create 操作没有已存在的数据项delete 操作中afterOperation的item为null源码中为undefined见packages/core/src/lib/core/mutations/index.ts的deleteSingle__delete 时传入item: undefined, originalItem: item若beforeOperation抛出异常操作返回错误数据不会写入数据库若afterOperation抛出异常数据仍保留在数据库中。因此afterOperation应仅用于“执行失败不构成关键问题”的副作用从源码看runSideEffectOnlyHookpackages/core/src/lib/core/hooks.ts对beforeOperation/afterOperation的执行顺序有明确规定先并行执行字段级 hooks再执行列表级 hooks。一个值得注意的细节是对于 create/update 操作字段级操作 hooks 只在原始输入中显式包含该字段时才执行通过检查inputData的 key 集合判断而 delete 操作时字段级 hooks 始终执行。与 Prisma 写入的关系在createSingle__/updateSingle__packages/core/src/lib/core/mutations/index.ts中可以看到完整的调用链访问控制 →resolveInputForCreateOrUpdate包含getResolvedData解析 validate校验→beforeOperation()→context.prisma[list.listKey].create/update(...)→afterOperation(result)。也就是说beforeOperation执行时 Prisma 尚未写入afterOperation执行时数据已落库。delete 操作则是校验 →beforeOperation→ Prisma delete →afterOperation。List Hooks 与 Field Hooks 的选择以上示例都是 list 级别的 hooks。Keystone 同样支持在单个字段上配置 hooks所有同名的 hooks 都可用参数也一致只是额外多一个fieldKey参数。字段级 hooks 适合表达字段专属规则。例如把邮件格式校验写成一个字段 hook代码会清晰得多import { config, list } from keystone-6/core; import { text } from keystone-6/core/fields; export default config({ lists: { User: list({ fields: { name: text(), email: text({ validation: { isRequired: true }, hooks: { validate: ({ addValidationError, resolvedData, fieldKey }) { const email resolvedData[fieldKey]; if (email ! undefined email ! null !email.includes()) { addValidationError(The email address ${email} provided for the field ${fieldKey} must contain an character); } }, }, }), }, }), }, });在packages/core/src/types/config/hooks.ts的类型定义中字段级 hooks 相比列表级 hooks 多出inputFieldData、itemField、resolvedFieldData、originalItemField等参数分别对应字段的原始输入、数据库当前值、解析后值与原值方便在单字段维度做精细化处理。官方示例hooks 的完整用法仓库中的 examples/hooks/schema.ts 是一个完整的演示 schema展示了 hooks 在生产场景下的组合用法值得参考createdAt字段的resolveInput.create自动写入当前时间updatedAt字段的resolveInput.update在更新时刷新时间戳list 级resolveInput.create/update从context.req中提取客户端 IP 和 User-Agent写入createdBy/updatedBy字段实现审计追踪list 级validate.create/update对标题、正文和反馈做敏感词过滤/profanity/i并在delete时检查preventDelete标记阻止删除beforeOperation记录将要写入的数据afterOperation分别针对 createinput → item、updateoriginalItem → item、deleteoriginalItem → deleted打印差异日志Hook 参数速查列表级与字段级 hook 的常用参数汇总如下完整签名见 Hooks API 参考参数说明listKey被操作的列表 keyfieldKey被操作的字段 key仅字段级 hookoperation操作类型create/update/deleteinputDatamutation 传入的data原始值delete 时为undefinedinputFieldData输入数据中该字段的值仅字段级 hookdelete 时为undefineditem数据库当前存储的数据项create 时为undefinedoriginalItem更新/删除前的原始数据项仅afterOperationcreate 时为undefinedresolvedData经过默认值、关系解析器、字段解析器与resolveInputhooks 处理后的数据delete 时为undefinedcontext发起操作的 KeystoneContext 对象addValidationError(msg)上报校验错误仅validatehook相关资源Hooks API Referencemutation 生命周期各阶段执行代码的完整参考包含每个 hook 的参数表与resolvedData解析阶段详解Hooks Guide本文对应的官方指南examples/hooks可运行的完整 hooks 示例 schema源码实现validate与runSideEffectOnlyHook的运行时实现mutation 执行链hook 在 create/update/delete 全流程中的实际调用位置【免费下载链接】keystoneThe superpowered headless CMS for Node.js — built with GraphQL and React项目地址: https://gitcode.com/gh_mirrors/key/keystone创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
任意文件下载漏洞挖掘|网络安全教程 30 个实战技巧从入门到精通 前言
文件下载漏洞(任意文件读取 / 目录穿越下载)是 Web 渗透测试中出现频率最高、利用门槛最低、危害极大的经典高危漏洞。
该漏洞原理极其简单:后端未对用户可控的文件参数做路径校验、过滤、白名单限制,导致攻击者可以穿越目… · 2026/9/24 15:01:42
address4cj API速查手册:核心类、函数接口与类型扩展完整参考 address4cj API速查手册:核心类、函数接口与类型扩展完整参考 【免费下载链接】address4cj 处理地址表示、验证和格式化。 项目地址: https://gitcode.com/Cangjie-SIG/address4cj
address4cj 是一款面向仓颉语言的地址处理库,用于全球地址的表示… · 2026/9/24 15:01:42
Comp AI CRM 前端样式规范:shadcn/ui 语义化 Styling Customization 实战指南 后端前端CRM人工智能AI Agent 【免费下载链接】crm Comp AI CRM is an open source, CRM designed for AI agents. Agentic-first CRM. 项目地址: https://gitcode.com/gh_mirrors/crm48/crm 点击查看 免费下载 本文以 Comp AI CRM 仓库内 .agents/skills/shadcn/r… · 2026/9/24 15:33:03
thumbor 的 strip_exif 过滤器:按需移除输出图片 EXIF 元数据 后端图像处理 【免费下载链接】thumbor thumbor is an open-source photo thumbnail service by globo.com 项目地址: https://gitcode.com/gh_mirrors/th/thumbor 点击查看 免费下载 导读
strip_exif 是 thumbor 内置图片过滤器之一,用于在图片处理管… · 2026/9/24 15:33:03
标签打印软件选型:别只看低价,正版授权的长期价值 很多制造企业在采购Bartender、NiceLabel、Codesoft这类条码标签设计软件,工业标签打印软件时,第一反应是先找全网最低价。但实际接触下来会发现,市面上不少小代理给出的报价远低于官方指导价,背后往往藏着复用授权、共享激活码、… · 2026/9/24 15:33:03
作为程序员的我,用工程思维解决了摄影学习的最大痛点 问题定义:摄影学习的"黑盒困境"
作为一个写了十年代码的程序员,我最受不了的就是没有反馈的学习过程。写代码有编译错误提示,有单元测试,有性能分析工具,每一步都能看到明确的反馈。但学摄影完全不一样&… · 2026/9/24 15:32:51
nom 8.0 演进全解析:从 CHANGELOG 看 Rust 解析器组合框架的十年架构变迁 开发工具 【免费下载链接】nom Rust parser combinator framework 项目地址: https://gitcode.com/gh_mirrors/no/nom 点击查看 免费下载 nom 是 Rust 生态中最具代表性的解析器组合框架(parser combinator framework)之一,本仓库… · 2026/9/24 15:32:44
Semi Design 图标(Icon)组件完全指南:图标集体系、尺寸旋转、双色多色着色与自定义方案 前端UI组件设计系统 【免费下载链接】semi-design 🚀A modern, comprehensive, flexible design system and React UI library, AI-friendly built-in.🎨Provide 3000 Design Tokens, easy to build your design system. Make Semi Design to Any Design… · 2026/9/24 15:32:38
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44