示例说明
手写 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);