- 后端
【免费下载链接】express-validator
An express.js middleware for validator.js.
导读
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),其返回类型为RunnableValidationChains<C>,即“校验链数组 + 一个run(req)方法”的组合体(见 src/middlewares/schema.ts#L143-L145):
export type RunnableValidationChains<C extends ValidationChainLike> = C[] & { run(req: Request): Promise<ResultWithContext[]>; };基础用法:注册到 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#L211):
const 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-L138):
BaseParamSchema:字段级通用配置,包括in、errorMessage、optional;ValidatorsSchema/SanitizersSchema:内置校验器与清洗器的配置(每个键的值可以是boolean或带options等配置的对象);ParamSchema<T>:三者交叉,并允许任意扩展键T(当键不在内置范围内时,其值为CustomValidatorSchemaOptions或CustomSanitizerSchemaOptions);Schema<T> = Record<string, ParamSchema<T>>:字段名到字段 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 });特别提醒(数组参数):如果传给校验器的唯一参数本身是一个数组,那么它必须再被包裹一层数组。典型场景是isIn:
checkSchema({ weekend: { // 👎 会翻译成 `isIn('saturday', 'sunday')` —— 错误 isIn: { options: ['saturday', 'sunday'] }, // 👍 会翻译成 `isIn(['saturday', 'sunday'])` —— 正确 isIn: { options: [['saturday', 'sunday']] }, }, });源码中这一逻辑由_.castArray(entry[1].options)实现(见 src/middlewares/schema.ts#L250):options总会先被规范成数组,再作为展开参数传给校验器,因此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-L209(issue #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),并作为protectedNames('errorMessage'、'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-L62):
checkSchema({ 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-validator
An express.js middleware for validator.js.
相关推荐
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 的
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考