基于 `checkSchema()` 的声明式请求校验:express-validator 模式化验证完全指南

发布时间:2026/10/10 2:03:05
基于 `checkSchema()` 的声明式请求校验:express-validator 模式化验证完全指南 后端【免费下载链接】express-validatorAn express.js middleware for validator.js.项目地址https://gitcode.com/gh_mirrors/ex/express-validator点击查看免费下载导读checkSchema()是 express-validator 提供的一种声明式校验 API它允许你用一个纯对象schema一次性描述多个字段的校验、清洗规则并自动为每个字段生成对应的校验链ValidationChain直接作为中间件挂载到 express.js 路由上。本文以官方文档 docs/api/check-schema.md 为骨架结合 src/middlewares/schema.ts 的源码实现与 src/middlewares/schema.spec.ts 的测试用例系统讲解 Schema 的全部核心要素——内置校验器/清洗器的配置方式、options/bail/if/negated/errorMessage等修饰符、字段级修饰符in/optional、自定义校验器与清洗器的两种写法以及手动运行checkSchema()的实战方案。checkSchema()是什么函数签名checkSchema(schema: Schema, defaultLocations?: Location[]): ValidationChain[] ContextRunnercheckSchema()会根据传入的schema生成一个校验链列表每个字段对应一条ValidationChain这个列表既可以作为 express.js 路由中间件使用又因为同时实现了ContextRunner接口而可以被手动运行。源码中checkSchema是通过createCheckSchema(check)创建的见 src/middlewares/schema.ts#L286其返回类型为RunnableValidationChainsC即“校验链数组 一个run(req)方法”的组合体见 src/middlewares/schema.ts#L143-L145export type RunnableValidationChainsC extends ValidationChainLike C[] { run(req: Request): PromiseResultWithContext[]; };基础用法注册到 express 路由最典型的使用方式是把checkSchema()的结果直接作为中间件传入路由app.post( /signup, checkSchema({ email: { isEmail: true }, password: { isLength: { options: { min: 8 } } }, }), (req, res) { // Handle request }, );默认的校验位置request locations默认情况下schema 中所有字段都会在全部请求位置中校验即body、cookies、headers、params和query五处都会检查。这一点在源码中有直接体现checkSchema的defaultLocations参数默认值为validLocations见 src/middlewares/schema.ts#L147 与 src/middlewares/schema.ts#L211const validLocations: Location[] [body, cookies, headers, params, query];对应的测试用例也验证了这一点见 src/middlewares/schema.spec.ts#L28-L40空 schema 生成的 chain 其locations正是上述五个位置。如果你只想在部分位置校验可以通过第二个参数指定。例如只在body和query中校验checkSchema(schema, [body, query]);如果需要按字段精细化控制校验位置则使用字段级的in属性它优先于defaultLocations参数。源码中ensureLocations()函数见 src/middlewares/schema.ts#L288-L296的逻辑是如果config.in存在字符串或数组则使用它否则回退到defaultLocations最后再过滤掉非法位置。手动运行checkSchema()checkSchema()返回的本质上是一个中间件直接传给 express 路由最理想。但由于它也实现了ContextRunner接口你也可以在自定义的中间件或路由处理器中手动运行它app.post(/signup, async (req, res) { const results await checkSchema({ email: { isEmail: true }, password: { isLength: { options: { min: 8 } } }, }).run(req); const hasErrors results.some(result !result.isEmpty()); if (hasErrors) { const errors results.flatMap(result result.array()); return res.status(400).json({ errors }); } });几点值得注意的细节每个字段对应一个独立的结果Result所以要用results.some(...)判断是否有任何字段出错用results.flatMap(result result.array())把所有错误扁平化聚合成一个数组。源码中run方法被实现为runAllChains(req, chains)见 src/middlewares/schema.ts#L272即并行运行所有校验链并返回各自的结果数组。手动运行相关更完整的方案如“顺序执行、遇到第一个失败即停止”的通用validate()包装器、以及用.if()做条件校验可参考 docs/guides/manually-running.md。另外ContextRunner的run(req, options?)支持{ dryRun: boolean }选项默认会把校验/清洗结果回写到req影响validationResult(req)与已被清洗字段的取值设置dryRun: true则只运行校验并返回结果而不回写详见 docs/api/misc.md#contextrunner。:::tip更多手动运行校验链的细节参见指南 docs/guides/manually-running.md。:::Schema 的结构与类型体系Schema本质是一个从字段路径到字段 schema 的映射对象。字段路径field path决定了选择请求中的哪些字段字段 schema 决定了这些字段如何被校验与清洗。字段路径的语法如addresses.work.country、siblings[0].name、websites[www.example.com]以及通配符*与 globstar**参见 docs/guides/field-selection.md。一个字段 schema 的键key可以是以下四类中的一种或多种组合内置校验器built-in validators内置清洗器built-in sanitizers字段修饰符field modifiers其他任意名称 —— 表示自定义校验器或自定义清洗器如果键不属于以上任何一类则它必须是一个自定义 schema即以任意名称包裹custom/customSanitizer的写法。从源码看TypeScript 类型体系如下见 src/middlewares/schema.ts#L92-L138BaseParamSchema字段级通用配置包括in、errorMessage、optionalValidatorsSchema/SanitizersSchema内置校验器与清洗器的配置每个键的值可以是boolean或带options等配置的对象ParamSchemaT三者交叉并允许任意扩展键T当键不在内置范围内时其值为CustomValidatorSchemaOptions或CustomSanitizerSchemaOptionsSchemaT Recordstring, ParamSchemaT字段名到字段 schema 的映射。checkSchema()的返回链还通过RunnableValidationChains类型标注了“数组 run”的组合形态。内置校验器Built-in Validators任何ValidationChain上的内置校验器如isEmail、isLength、isEmpty、isInt、notEmpty、matches等都可以直接作为字段 schema 的键使用。注意not与withMessage两个方法被源码明确排除在 schema 键之外见 src/middlewares/schema.ts#L169 与测试 src/middlewares/schema.spec.ts#L118-L133因为它们分别对应 schema 中的negated与errorMessage修饰符。值为true无参数开启如果内置校验器被设置为true表示无参数开启该校验checkSchema({ email: { isEmail: true }, password: { notEmpty: true }, });check(email).isEmail(); check(password).notEmpty();值为对象带配置开启校验器的值也可以是一个对象此时校验器将携带额外配置开启可配置的属性包括options、bail、if、negated、errorMessage。这些配置对应的源码类型定义在BaseValidatorSchemaOptions见 src/middlewares/schema.ts#L24-L46且if、negated会在校验器之前插入链中bail、errorMessage则在校验器之后追加见 src/middlewares/schema.ts#L242-L266。options设置校验器的参数。当有多个参数时options必须是数组只有一个参数时可以直接传值。checkSchema({ phone: { isMobilePhone: { options: [any, { strictMode: true }], }, }, password: { isLength: { options: { min: 8 }, }, }, });check(phone).isMobilePhone(any, { strictMode: true, }); check(password).isLength({ min: 8 });特别提醒数组参数如果传给校验器的唯一参数本身是一个数组那么它必须再被包裹一层数组。典型场景是isIncheckSchema({ weekend: { // 会翻译成 isIn(saturday, sunday) —— 错误 isIn: { options: [saturday, sunday] }, // 会翻译成 isIn([saturday, sunday]) —— 正确 isIn: { options: [[saturday, sunday]] }, }, });源码中这一逻辑由_.castArray(entry[1].options)实现见 src/middlewares/schema.ts#L250options总会先被规范成数组再作为展开参数传给校验器因此options: [[saturday, sunday]]最终展开为isIn([saturday, sunday])。bail如果当前校验器或之前任意校验器失败则停止继续运行后续校验链。等价于在链上使用.bail()。checkSchema({ email: { // 先运行 isEmail如果 email 不合法则下面的自定义校验器 checkEmailNotInUse 不会运行 isEmail: { bail: true }, custom: { options: checkEmailNotInUse }, }, });等价于check(email).isEmail().bail().custom(checkEmailNotInUse);源码在链上调用chain.bail(validatorConfig.bail true ? {} : validatorConfig.bail)见 src/middlewares/schema.ts#L263-L264因此bail除了布尔值外还支持.bail()的完整选项对象例如{ level: request }表示请求级 bail——不仅停止当前链还停止当前请求上后续所有校验链的运行。测试 src/middlewares/schema.spec.ts#L396-L412 验证了请求级 bail第一条链失败后schema.run(req)只返回了一个结果。if为字段的校验器是否继续运行添加条件。等价于链上的.if()。if在当前校验器之前应用即条件不满足时该校验器及其后的校验器都不会运行。checkSchema({ newPassword: { exists: { // 用自定义校验函数作为条件 if: (value, { req }) !!req.body.oldPassword, // 或者用一条校验链作为条件 if: body(oldPassword).notEmpty(), }, }, });源码在链上先执行validatorConfig.if chain.if(validatorConfig.if)再执行该校验器本身见 src/middlewares/schema.ts#L245。测试 src/middlewares/schema.spec.ts#L163-L177 验证了当if条件返回 false 时整条链停止执行、无错误产生。negated取反校验器的结果。等价于链上的.not()。checkSchema({ password: { // 检查 password 不为空 isEmpty: { negated: true }, }, });源码中validatorConfig.negated chain.not()同样在调用校验器之前执行见 src/middlewares/schema.ts#L246。测试 src/middlewares/schema.spec.ts#L230-L241 表明isEmpty: { negated: true }遇到空字符串时会报错。errorMessage{#validator-errormessage}为该校验器设置错误消息。等价于链上的.withMessage()。checkSchema({ email: { isEmail: { errorMessage: Must be a valid e-mail address, }, }, });check(email).isEmail().withMessage(Must be a valid e-mail address);需要注意的是errorMessage只能作用于校验器。源码只在isStandardValidator/isCustomValidator分支后调用chain.withMessage(...)见 src/middlewares/schema.ts#L265清洗器上的errorMessage会被忽略——测试 src/middlewares/schema.spec.ts#L192-L209issue #548专门验证了这一点。内置清洗器Built-in Sanitizers任何ValidationChain上的内置清洗器如trim、normalizeEmail、escape、whitelist、toInt等都可以作为字段 schema 的键。值为true无参数开启checkSchema({ query: { trim: true }, });check(query).trim();值为对象带配置开启清洗器的值也可以是对象此时它被开启并携带额外配置options与校验器相同设置清洗器的参数。多个参数时必须是数组单个参数可以直接传值。checkSchema({ email: { normalizeEmail: { options: { gmail_remove_subaddress: true }, }, }, });check(email).normalizeEmail({ gmail_remove_subaddress: true, });源码中清洗器与校验器走的是同一条配置管线options同样经过_.castArray后展开传给清洗器见 src/middlewares/schema.ts#L249-L252。测试 src/middlewares/schema.spec.ts#L135-L161 验证了whitelist: { options: [a] }会把字段值清洗为a。字段 Schema 修饰符以下属性可以在字段 schema 中指定用于修改该字段的通用行为。它们来自源码中的BaseParamSchema见 src/middlewares/schema.ts#L92-L113并作为protectedNameserrorMessage、in、optional见 src/middlewares/schema.ts#L148在遍历时被跳过、不会当作校验器/清洗器处理。in定义该字段在哪些位置request location被校验。例如校验字段存在于 body 或 query 字符串中checkSchema({ field: { in: [body, query], exists: true, }, });in可以是单个位置字符串也可以是位置数组它优先于checkSchema()的defaultLocations参数。测试 src/middlewares/schema.spec.ts#L53-L71 分别验证了in: body与in: [params, body]两种写法对locations的影响。合法的位置值是Location类型body | cookies | headers | params | query见 docs/api/misc.md#location。errorMessage{#field-errormessage}设置字段的默认错误消息仅在某个校验器没有在自己的配置中指定errorMessage时使用。等价于check(field, message)的第二个参数。checkSchema({ password: { errorMessage: The password must be at least 8 characters, and must contain a symbol, isLength: { options: { min: 8 } }, matches: { options: /[-_$#]/ }, }, });check(password, The password must be at least 8 characters, and must contain a symbol) .isLength({ min: 8 }) .matches(/[-_$#]/);源码中config.errorMessage被传给createChain即check()从而作为ContextBuilder的默认消息见 src/middlewares/schema.ts#L214-L218 与 src/middlewares/check.ts#L12-L21。测试 src/middlewares/schema.spec.ts#L17-L25 验证了字段级errorMessage会体现在生成的 context 消息中。optional在字段上设置可选修饰符。等价于链上的.optional()。checkSchema({ query: { optional: true, isLength: { options: { min: 3 } }, }, });check(query).optional().isLength({ min: 3 });optional的值可以是布尔值也可以是一个带options的对象。源码中通过chain.optional(config.optional true ? true : config.optional.options)处理见 src/middlewares/schema.ts#L221-L223并且注释明确指出“optional 在链中的位置无关紧要”。options支持{ values: undefined | null | falsy, nullable, checkFalsy }其中nullable/checkFalsy为已弃用别名。测试 src/middlewares/schema.spec.ts#L211-L228 验证了optional: true产生undefined级别、optional: { options: { checkFalsy: true, nullable: true } }产生falsy级别。自定义校验器/清洗器Custom validators使用checkSchema()定义自定义校验器或清洗器有两种方式。方式一custom/customSanitizer键在字段 schema 中直接设置custom或customSanitizer。它们与内置校验器/清洗器在 schema 中的用法完全一致同样支持options、bail、if、negated、errorMessage等配置对应源码类型CustomValidatorSchemaOptions见 src/middlewares/schema.ts#L57-L62checkSchema({ email: { custom: { options: checkIfEmailExists, bail: true, }, customSanitizer: { options: removeEmailAttribute, }, }, });等价于check(email).custom(checkIfEmailExists).bail().customSanitizer(removeEmailAttribute);这种方式虽然可行但每个字段只能设置一个custom和一个customSanitizer。原因很简单JavaScript 对象不允许重复键可以重复写但只有最后一个生效。如果你想在一个字段上挂多个自定义校验器/清洗器方式二就是为此设计的。方式二任意命名键包裹custom/customSanitizer在字段 schema 中设置一个既不是内置校验器、也不是内置清洗器、也不是修饰符的任意键名其值必须是一个包含单个custom或customSanitizer函数的对象。上面的例子可以改写为checkSchema({ email: { emailNotInUse: { custom: checkEmailNotInUse, bail: true, }, removeEmailAttribute: { customSanitizer: removeEmailAttribute, }, }, });这样同一个字段上就可以挂多个自定义校验器/清洗器了。源码中的识别逻辑是四步类型守卫见 src/middlewares/schema.ts#L164-L209先判断是否是标准校验器isStandardValidator、标准清洗器isStandardSanitizer、再判断是否是自定义校验器isCustomValidator键值对象且含custom函数、自定义清洗器isCustomSanitizer。对于未知的键源码会输出警告并跳过见 src/middlewares/schema.ts#L236-L238相关行为由测试 src/middlewares/schema.spec.ts#L103-L116 覆盖。:::info自定义校验器/清洗器的名称不会被checkSchema()使用不同 schema 之间使用相同的自定义名称不会产生冲突。:::需要特别注意不能在内置校验器/清洗器的键下放置custom/customSanitizer——例如isInt: { custom: fn }会被当作isInt的标准配置处理custom函数不会被执行见测试 src/middlewares/schema.spec.ts#L262-L278 与 src/middlewares/schema.spec.ts#L299-L315。值得留意的边界行为与陷阱结合源码与测试以下几个行为值得在实际使用中注意值为false的键会被跳过schema 中isInt: false这样的“显式关闭”写法不会产生警告也不会加入链见 src/middlewares/schema.spec.ts#L88-L101。未知键会触发 console 警告isBla: true之类的拼写错误或不受支持的键会打印express-validator: schema of ... has unknown validator/sanitizer ...并被跳过见 src/middlewares/schema.ts#L236-L238。not与withMessage不能作为 schema 键它们分别用negated与errorMessage替代见 src/middlewares/schema.spec.ts#L118-L133。falsy 的options值会正常传递例如default: { options: 0 }会把0正确传给清洗器见 src/middlewares/schema.spec.ts#L429-L441。optional不受链位置影响无论写在 schema 的哪里它都影响整个字段对值的解释方式源码 src/middlewares/schema.ts#L220-L223 中在遍历键之前就处理了optional。总结checkSchema()是 express-validator 中把“多条校验链”浓缩成“一个声明式对象”的入口字段选择由字段路径语法.、[]、*、**完成校验与清洗由内置的 validators/sanitizers值为true或带options的对象完成行为微调由bail、if、negated、errorMessage、in、optional等修饰符完成自定义逻辑通过custom/customSanitizer或任意命名键包裹的方式注入返回的ValidationChain[] ContextRunner既能直接作为中间件使用也能通过.run(req)手动执行与oneOf()、checkExact()、validationResult()、matchedData()等 API 组合出完整的请求校验方案。如果你想进一步深挖可以从 src/middlewares/schema.ts 的createCheckSchema工厂函数入手理解 schema 是如何被逐键翻译为链上调用序列的src/middlewares/schema.spec.ts 则是验证这些行为的最佳参考。赞分享后端【免费下载链接】express-validatorAn express.js middleware for validator.js.项目地址https://gitcode.com/gh_mirrors/ex/express-validator点击查看免费下载相关推荐express-validator 基于 Schema 的声明式请求校验checkSchema() 完整实战指南express validator 基于 Schema 的声明式请求校验 checkSchema 完整实战指南 express validator 的 Sch后端Buzz Mac 安装报错3 步选对架构跑通Buzz Mac 安装报错3 步选对架构跑通 你在 Mac 上装 Buzz安装报错、提示已损坏或者装完转录慢得离谱多半是下载来源和芯片架构没对上。照后端express-validator 中的 Schema 校验checkSchema 声明式校验完全指南express validator 中的 Schema 校验checkSchema 声明式校验完全指南 本篇指南围绕 express validator 的后端上一篇Infer 问题抑制机制全解析infer-ignore 与 infer-ignore-every 的使用、通配符与实现原理下一篇EOS 钱包导入格式WIF规范私钥编码、解码校验与 keosd 钱包实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询