← 中文资源

简体中文版本

AJV OpenAPI 模式校验中间件

用 AJV 编译 OpenAPI 组件 JSON Schema 校验请求体:一次编译多次执行,全部错误聚合返回,附加字段自动剥离。

阅读英文原文

示例说明

手写 if 校验在字段增多后必然遗漏,且与 API 文档脱节。JSON Schema 是契约的形式化表达:required 锁必填、enum 锁取值域、format 校验邮箱格式、additionalProperties 剥离未知字段防止注入意外键。AJV 把 schema 编译成可执行函数,首次编译后校验开销是微秒级,allErrors 让一次请求返回全部问题。

技术要点

  • compile 一次生成高速校验函数
  • allErrors 聚合而非首错即止
  • removeAdditional 兜底剥离多余字段

代码解读

ajv 实例开启类型矫正、剥离附加字段与全错误收集,addFormats 启用 email 等标准格式;schemaMiddleware 是通用工厂,把编译产物包成可挂载的中间件,错误映射成带路径的响应结构。两段验证演示合法载荷通过、非法载荷返回路径化的错误清单。

使用建议

schema 应作为 OpenAPI 规范的单一来源,接口文档与运行时校验共享同一份定义。coerceTypes 慎用于布尔转换。错误响应不暴露 schema 本身,只给定位信息。

代码示例

import Ajv from 'ajv';
import addFormats from 'ajv-formats';

const ajv = new Ajv({ allErrors: true, coerceTypes: true, removeAdditional: true });
addFormats(ajv);

const schemas = {
  CreateUser: {
    type: 'object',
    additionalProperties: false,
    required: ['email', 'role'],
    properties: {
      email: { type: 'string', format: 'email' },
      role: { type: 'string', enum: ['admin', 'editor', 'viewer'] },
      display_name: { type: 'string', maxLength: 60 },
    },
  },
};

const validate = ajv.compile({
  $ref: '#/components/schemas/CreateUser',
  components: { schemas },
});

function schemaMiddleware(validator) {
  return (req, res, next) => {
    if (!validator(req.body)) {
      return res.status(400).json({
        error: 'schema validation failed',
        details: (validator.errors ?? []).map((e) => ({
          path: e.instancePath || e.params?.missingProperty ?? '',
          message: e.message,
        })),
      });
    }
    next();
  };
}

const ok = validate({ email: '[email protected]', role: 'editor' });
const bad = validate({ email: 'not-an-email', role: 'hacker', extra: 1 });
console.log('valid:', ok, 'errors:', bad ? 0 : validate.errors.length);