恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
express-validator ValidationChain 完全指南:内置校验器、净化器与修饰器精讲
首页
资讯中心
/
express-validator ValidationChain 完全指南:内置校验器、净化器与修饰器精讲
express-validator ValidationChain 完全指南:内置校验器、净化器与修饰器精讲
发布时间:2026/10/10 5:10:09
后端【免费下载链接】express-validatorAn express.js middleware for validator.js.项目地址https://gitcode.com/gh_mirrors/ex/express-validator点击查看免费下载ValidationChain 是 express-validator 的核心抽象由body()、param()、query()、check()等函数创建将针对某个字段的校验与净化规则封装为一个既是可链式调用的 API、又是可直接挂载到 Express 路由的中间件。本文以 v7.2.0 官方 API 文档为主线逐一讲解内置校验器、内置净化器与修饰器的签名、语义与实战代码并结合本仓库源码src/chain/*、src/context-items/*揭示其底层执行原理帮助你写出可精确控制、可复用的字段校验逻辑。一、什么是 ValidationChain从接口到三种用法ValidationChain是一个复合 TypeScript 接口它同时继承Validators、Sanitizers、ContextHandler与ContextRunner并且自身是一个 Express 中间件函数可调用签名(req, res, next) void持有底层的builder: ContextBuilder// src/chain/validation-chain.ts#L8-L15 export interface ValidationChain extends ValidatorsValidationChain, SanitizersValidationChain, ContextHandlerValidationChain, ContextRunner { (req: Request, res: any, next: (error?: any) void): void; builder: ContextBuilder; }同一文件还导出了ValidationChainLike类型——它是ValidationChain的宽松副本允许返回链自身的方法返回任意值常用于类型化既接受标准链、也接受自定义链的函数。ValidationChain 有三种典型用法作为 Express 路由中间件校验会随请求自动执行作为其他 API 的参数如oneOf()、checkExact()见 one-of.md 与 check-exact.md独立手动运行通过ContextRunner的run()完全控制校验时机与方式见 manually-running.md。如果你要编写一个接收ValidationChain的函数类型可以直接导入import { ValidationChain } from express-validator;关于链式调用的整体设计可先阅读 The Validation Chain 指南ValidationChain 的每个方法都会返回链自身方法链模式因此校验规则可以从左到右自然阅读但它也有一个重要特性——链是可变mutable的复用链时应通过工厂函数返回新链避免在已注册的路由上二次追加方法导致副作用。二、内置校验器Built-in validators.custom()custom(validator: (value, { req, location, path, pathValues }) any): ValidationChain为链添加一个自定义校验函数。字段视为有效的条件是自定义校验器返回真值truthy或返回的 Promise 被 resolve。反之返回假值、返回 reject 的 Promise、或函数抛错字段都会被判为无效。最常见的场景是检查邮箱是否已被注册若存在则抛出错误app.post( /signup, body(email).custom(async value { const existingUser await Users.findUserByEmail(value); if (existingUser) { throw new Error(E-mail already in use); } }), (req, res) { // Handle request }, );如果字段是通过通配符或 globstar选中的例如products.*.quantity可以借助pathValues拿到通配符实际匹配到的值用于引用同一对象中的其他属性app.post( /purchase, [ body(products.*.quantity).custom((quantity, { req, pathValues }) { const index Number(pathValues[0]); const { id } req.body.products[index]; if (getProductStock(id) quantity) { throw new Error(Theres not enough of product ${id} in stock); } }), ], (req, res) { // Handle request }, );源码原理CustomValidation在run()中先执行自定义函数并await其结果然后区分普通值与Promise两种判定路径——普通返回值直接取真值判定对于 Promise只要 resolve 即视为通过src/context-items/custom-validation.ts#L10-L34。抛出的错误会被捕获并作为该字段的错误消息记录err instanceof Error ? err.message : err。req、location、path、pathValues组成的Meta对象在 base.ts 中定义。.exists()exists(options?: { values?: undefined | null | falsy, checkNull?: boolean, checkFalsy?: boolean }): ValidationChain校验字段是否存在。哪些值算不存在由options.values决定默认是undefinedoptions.values行为undefinedundefined值视为不存在nullundefined和null值视为不存在falsy假值空字符串、0、false、null、undefined都视为不存在options.checkNull与options.checkFalsy是已弃用选项分别等价于把options.values设为null与falsy。注意只有在你没有添加任何其他校验器或净化器时才需要显式调用.exists()。源码原理ValidatorsImpl.exists()将上述三种模式直接映射为三个等价的内置判定src/chain/validators-impl.ts#L39-L50value !!valuefalsy 模式value value ! nullnull 模式value value ! undefinedundefined 模式默认.isArray()isArray(options?: { min?: number; max?: number }): ValidationChain校验值是否为数组语义与原生Array.isArray(value)一致。同时可校验数组长度长度需 options.min且/或 options.max。// 校验 friends 是数组 body(friends).isArray(); // 校验 ingredients 是长度 0 的数组 body(ingredients).isArray({ min: 0 }); // 校验 team_members 是长度 0 且 10 的数组 check(team_members).isArray({ min: 0, max: 10 });源码原理实现直接内联为Array.isArray(value) (min/max 长度判定)src/chain/validators-impl.ts#L52-L59min/max 未提供时跳过对应判断。.isObject()isObject(options?: { strict?: boolean }): ValidationChain校验值是否为对象。例如{}、{ foo: bar }、new MyCustomClass()都能通过。当strict设为false时行为与纯 JavaScript 的typeof value object一致——此时数组和null也被视为对象。源码原理isObject()默认strict: true判定逻辑为typeof value object value ! null !Array.isArray(value)strict为假时跳过后两个条件src/chain/validators-impl.ts#L61-L67。.isString()isString(): ValidationChain校验值是否为字符串等价于typeof value string。实现为this.custom(value typeof value string)src/chain/validators-impl.ts#L69-L71。.isULID()isULID(): ValidationChain校验值是否为 ULIDUniversally Unique Lexicographically Sortable Identifier格式。底层调用 validator.js 的validator.isULIDsrc/chain/validators-impl.ts#L347-L349。.notEmpty()notEmpty(): ValidationChain校验值是否为非空字符串长度大于等于 1等价于.not().isEmpty()。源码原理notEmpty()的实现就是先置位this.not()再调用this.isEmpty(options)src/chain/validators-impl.ts#L73-L76。注意isEmpty底层来自 validator.js并接受IsEmptyOptions。标准校验器Standard validators除了上述内置校验器ValidationChain 还暴露了validator.js 提供的全部标准校验器覆盖从常用的isEmail、isLength、isIn到小众的isISBN、isMultibyte、isJWT等数十个方法。完整签名清单见 _validators.md这里摘录几个典型用法// 常见校验 body(email).isEmail(); body(password).isLength({ min: 8, max: 64 }); query(type).isIn([user, posts]); body(age).isInt({ min: 0, max: 150 }); body(url).isURL(); body(id).isUUID(); // 带 locale 的校验 body(phone).isMobilePhone(zh-CN); body(name).isAlpha(en-US); body(card).isCreditCard(); // 哈希、邮政编号、IP 等 body(digest).isHash(sha256); body(code).isPostalCode(CN); body(ip).isIP(4);标准校验器的类型声明完整列于 src/chain/validators.tscontains到matches共一百多个方法实现则逐一委托给 validator.js 并包装为StandardValidation上下文项src/chain/validators-impl.ts#L79-L81。重要语义validator.js 只处理字符串。因此使用标准校验器/净化器时express-validator 会先把字段值转成字符串再交给 validator.jsDate对象使用toISOString()的结果null、undefined、NaN转为空字符串实现了自定义toString()的对象使用该方法返回值其他对象使用默认Object.prototype.toString()其余值布尔、数字等原样转成字符串。数组的每个元素会逐个独立地按上述规则校验/净化见 Sanitization 实现 与 The Validation Chain 指南。例如body(ids).isNumeric()在req.body.ids [5, 33, abc, def]时会为abc和def各记录一条错误。三、内置净化器Built-in sanitizers.customSanitizer()customSanitizer(sanitizer: (value, { req, location, path, pathValues }) any): ValidationChain添加自定义净化函数其返回值会成为字段的新值app.post(/object/:id, param(id).customSanitizer((value, { req }) { // 本应用中用户使用 MongoDB 风格的对象 ID其余则使用数字 return req.query.type user ? ObjectId(value) : Number(value); })), (req, res) { // Handle request });源码原理customSanitizer()把函数包装为custom: true的Sanitization项运行后通过context.setData(path, newValue, location)把新值写回请求对象供后续校验器、路由处理器乃至其他中间件读取src/context-items/sanitization.ts#L19-L27、src/chain/sanitizers-impl.ts#L13-L16。.default()default(defaultValue: any): ValidationChain当字段值为空字符串、null、undefined或NaN之一时用defaultValue替换字段值app.post(/, body(username).default(foo), (req, res, next) { // bar bar // foo // undefined foo // null foo // NaN foo });注意若默认值是对象会被深拷贝_.cloneDeep以避免不同请求之间共享同一引用。源码原理default()本质是customSanitizer的语法糖判定逻辑为[undefined, null, NaN, ].includes(value)命中则返回_.cloneDeep(default_value)src/chain/sanitizers-impl.ts#L17-L21。.replace()replace(valuesFrom: any[], valueTo: any): ValidationChain当字段当前值出现在valuesFrom中时把值替换为valueToapp.post(/, body(username).replace([bar, BAR], foo), (req, res, next) { // bar_ bar_ // bar foo // BAR foo console.log(req.body.username); });注意与.default()相同若替换值是对象也会被深拷贝以避免跨请求共享引用。源码原理replace()会先把非数组的values_from包装成数组再通过values_to_replace.includes(value)判定并返回_.cloneDeep(new_value)src/chain/sanitizers-impl.ts#L22-L29。.toArray()toArray(): ValidationChain把值转换为数组已经是数组则原样保留undefined变为空数组。实现为value ! undefined ((Array.isArray(value) value) || [value]) || []src/chain/sanitizers-impl.ts#L58-L62。.toLowerCase()/.toUpperCase()toLowerCase(): ValidationChain toUpperCase(): ValidationChain分别把字符串转小写/大写若值不是字符串则不做任何操作src/chain/sanitizers-impl.ts#L75-L80。标准净化器Standard sanitizersValidationChain 同样暴露 validator.js 的全部标准净化器签名清单见 _sanitizers.md。常用示例// 字符串清洗与转换 body(name).trim(); // 去除首尾空白 body(name).ltrim().rtrim(); // 分别去除左/右侧空白 body(html).escape(); // HTML 转义 body(text).stripLow(); // 去除 ASCII 控制字符 body(email).normalizeEmail(); // 规范化邮箱 body(age).toInt(); // 转整数 body(price).toFloat(); // 转浮点数 body(flag).toBoolean(); // 转布尔 body(date).toDate(); // 转 Date body(payload).blacklist(); // 移除指定字符 body(chars).whitelist(abc123); // 仅保留白名单字符完整类型声明见 src/chain/sanitizers.ts实现统一通过addStandardSanitization包装为custom: false的Sanitization项src/chain/sanitizers-impl.ts#L32-L35。四、修饰器Modifiers控制链的执行行为.bail()bail(options?: { level: chain | request }): ValidationChain参数名称说明options.level停止校验链的粒度默认chain当之前任一校验器失败时停止执行当前校验链。典型用途避免已知会失败的场景下继续触发访问数据库或外部 API 的自定义校验器昂贵的副作用。.bail()可在同一链上多次使用body(username) .isEmail() // 不是邮箱就到此为止 .bail() .custom(checkDenylistDomain) // 域名不在白名单就不去查是否已注册 .bail() .custom(checkEmailExists);当level设为request时一旦出错当前请求上的后续所有校验链都不会再运行app.get( /search, query(query).notEmpty().bail({ level: request }), // 如果 query 为空下面这些校验链不会运行 query(query_type).isIn([user, posts]), query(num_results).isInt(), (req, res) { // Handle request }, );注意使用 request 级 bail 时oneOf()one-of.md与checkExact()check-exact.md这类函数可能变慢因为原本可以并行运行的校验链被迫串行执行。源码原理.bail()在level request时先通过builder.setRequestBail()标记整个请求停止再向链中追加一个Bail上下文项Bail.run()在检测到context.errors.length 0时抛出ValidationHalt从而中断后续校验src/chain/context-handler-impl.ts#L12-L18、src/context-items/bail.ts#L5-L12。.if()if(condition: CustomValidator | ContextRunner): ValidationChain为链添加一个是否继续校验该字段的条件。条件可以是自定义校验器CustomValidator也可以是ContextRunner实例见 misc.mdbody(newPassword) // 只有提供了旧密码才校验 .if((value, { req }) req.body.oldPassword) // 或者改用一条校验链作为条件 .if(body(oldPassword).notEmpty()) // 只有当 oldPassword 提供了新密码长度才会被校验 .isLength({ min: 6 });源码原理ContextHandlerImpl.if()依据条件类型分派带run方法的视为ContextRunner包装为ChainCondition函数则包装为CustomCondition两者都不是会抛出express-validator: condition is not a validation chain nor a functionsrc/chain/context-handler-impl.ts#L20-L29。CustomCondition在条件返回假值或 Promise reject/抛错时抛出ValidationHalt中断后续校验src/context-items/custom-condition.ts#L8-L21。.not()not(): ValidationChain取反链中下一个校验器的结果check(weekday).not().isIn([sunday, saturday]);源码原理not()只是置位negateNext true随后创建的校验项会携带该标记addItem()在追加后会重置negateNext因此not()只影响紧随其后的一个校验器src/chain/validators-impl.ts#L14-L27。在CustomValidation.run()中取反逻辑为failed this.negated ? actualResult : !actualResultsrc/context-items/custom-validation.ts#L15。.optional()optional(options?: boolean | { values?: undefined | null | falsy, nullable?: boolean, checkFalsy?: boolean, }): ValidationChain将当前校验链标记为可选可选字段会依据其值跳过校验而不是让校验失败。哪些值算可选由options.values决定默认undefinedoptions.values行为undefinedundefined值可选nullundefined和null值可选falsy假值空字符串、0、false、null、undefined都可选options.nullable与options.checkFalsy是弃用选项分别等价于把options.values设为null或falsy。若options为false字段不再可选。关键语义与校验器和净化器不同.optional()不区分位置——无论出现在链的哪个位置它都以相同方式影响值的解释。因此下面两种写法完全等价body(json_string).isLength({ max: 100 }).isJSON().optional().body(json_string).optional().isLength({ max: 100 }).isJSON().源码原理optional()把选项归一化为undefined | null | falsy | false四种取值并通过builder.setOptional(value)写入上下文构建器src/chain/context-handler-impl.ts#L31-L47该值最终决定哪些字段值会跳过校验。.hide()hide(hiddenValue?: string): ValidationChain在validationResult()返回的错误中隐藏该字段的值。当字段是敏感信息如 API Key时调用此方法可防止泄露若传入hiddenValue则用它替换错误中该字段的值。名称说明hiddenValue用于替换字段值的字符串// 错误中省略该字段值 query(api_key).custom(isValidKey).hide(); // 错误中用 ***** 替换字段值 query(api_key).custom(isValidKey).hide(*****);源码原理.hide()调用builder.setHidden(true, hiddenValue)把隐藏标记与可选的替换字符串写入上下文src/chain/context-handler-impl.ts#L49-L52错误格式化时据此处理value相关错误 API 见 validation-result.md。.withMessage()withMessage(message: any): ValidationChain为前一个校验器设置错误消息。message可以是任意值也可以是动态生成消息的工厂函数FieldMessageFactory基于字段值生成消息。实现上直接写入lastValidator.messagesrc/chain/validators-impl.ts#L29-L32。body(email) .isEmail() .withMessage(Please provide a valid e-mail address); // 动态消息 body(age) .isInt({ min: 18 }) .withMessage((value) Expected age 18, got ${value});五、链路顺序与复用两个实战易错点结合 The Validation Chain 指南 中的讲解使用 ValidationChain 时有两点必须牢记顺序几乎总是重要的。方法按书写顺序依次执行因此下面两条链的结果不同// 先判非空、再 trim全空格的 search_query 能通过校验但 trim 后字段变空误报 query(search_query).notEmpty().trim(); // 先 trim、再判非空更合理的顺序 query(search_query).trim().notEmpty();唯一的例外是.optional()它可以在任意位置生效。链是可变的复用需用工厂函数。直接保存链后再追加方法会导致副作用扩散到所有引用它的路由// 推荐函数返回新链 const createEmailChain () body(email).isEmail(); app.post(/login, createEmailChain(), handleLoginRoute); app.post(/signup, createEmailChain().custom(checkEmailNotInUse), handleSignupRoute); // 危险共享同一可变链对象signup 的 custom 校验会意外作用于 login // const baseEmailChain body(email).isEmail();六、总结ValidationChain 把字段校验组织为三类能力内置校验器.custom()、.exists()、.isArray()、.isObject()、.isString()、.isULID()、.notEmpty()负责判定值是否合法底层实现集中在 src/chain/validators-impl.ts内置净化器.customSanitizer()、.default()、.replace()、.toArray()、.toLowerCase()、.toUpperCase()负责转换值并写回请求实现见 src/chain/sanitizers-impl.ts对象类型值会自动深拷贝修饰器.bail()、.if()、.not()、.optional()、.hide()、.withMessage()控制链的执行时机、条件、取反与错误输出实现见 src/chain/context-handler-impl.ts。同时所有 validator.js 的标准校验器/净化器都以ValidationChain方法的形式暴露且标准校验器/净化器会先把值转为字符串数组逐元素处理。掌握这些 API 的签名与语义配合.bail({ level: request })、.optional()等修饰器即可构建出既安全又高效的 Express 请求校验管线。更多组合玩法可继续阅读 check.md创建链的入口函数与 validation-result.md错误结果的读取与格式化。赞分享后端【免费下载链接】express-validatorAn express.js middleware for validator.js.项目地址https://gitcode.com/gh_mirrors/ex/express-validator点击查看免费下载相关推荐express-validator 校验链ValidationChain权威指南内置校验器、净化器与修饰符全解析express validator 校验链ValidationChain权威指南内置校验器、净化器与修饰符全解析 ValidationChain 是 ex后端为什么Codex-X是Codex用户必备神器7大核心亮点全解析为什么Codex X是Codex用户必备神器7大核心亮点全解析 Codex X 是一款面向 OpenAI Codex 桌面端 / Codex CLI 的跨平台后端express-validator 7.2 sanitizer API 完全指南内置净化器与 ValidationChain 数据清洗实战express validator 7.2 sanitizer API 完全指南内置净化器与 ValidationChain 数据清洗实战 导读 本文以 ex后端上一篇Red-Baron 开源项目教程下一篇LaTeX2e First Aid 机制全解析内核如何为未更新的外部宏包提供过渡期修复创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考