关于 validation
面向 JavaScript 项目的 JSON Schema 验证技能,使用 AJV 指导开发者校验 API 输入和服务数据,并让 Schema、生成的 TypeScript 声明与文档保持一致。
主要功能
- 01基于 AJV 的 JSON Schema 验证
- 02请求体、查询和参数中间件
- 03查询参数类型转换
- 04生成 TypeScript 声明
- 05对齐 Schema 与数据库约束
适用场景
- 01校验 API 请求和表单提交
- 02写入数据库前检查数据
- 03定义服务间契约
- 04从 Schema 生成 TypeScript 声明
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ profpowell/ vanilla-breeze --skill validation安装后在 Claude Code 中输入/validation确认已加载。
详细介绍
JSON Schema 验证技能
使用 JSON Schema 定义和 AJV 运行时验证,在所有边界验证数据。
适用场景
- 创建接受用户输入的 API 端点
- 在服务端验证表单提交
- 在写入数据库前确保数据完整性
- 定义服务之间的契约
- 根据架构生成 TypeScript 类型
架构文件位置
架构文件位于 /schemas/ 目录,结构如下:
schemas/
common/ # Shared/reusable schemas
uuid.schema.json
error-response.schema.json
pagination.schema.json
entities/ # Domain entity schemas
user.schema.json # Full entity
user.create.schema.json # Create input (no id/timestamps)
user.update.schema.json # Partial update (all optional)
api/ # API-specific request schemas
login.schema.json
register.schema.json
架构命名约定
| 模式 | 示例 | 用途 |
|---|---|---|
{entity}.schema.json | user.schema.json | 包含所有字段的完整实体 |
{entity}.create.schema.json | user.create.schema.json | 创建输入(不含 id 和时间戳) |
{entity}.update.schema.json | user.update.schema.json | 部分更新(所有字段均为可选) |
{context}.schema.json | login.schema.json | 特定上下文的架构 |
编写架构
基本架构模板
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "entities/user.create",
"title": "Create User",
"description": "Schema for creating a new user",
"type": "object",
"required": ["email", "password"],
"properties": {
"email": {
"type": "string",
"format": "email",
"maxLength": 254,
"description": "User's email address"
},
"password": {
"type": "string",
"minLength": 8,
"maxLength": 128,
"description": "User's password (8-128 characters)"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100,
"description": "User's display name"
}
},
"additionalProperties": false
}
关键属性
| 属性 | 用途 |
|---|---|
$id | 用于引用的唯一标识符(例如,“entities/user.create”) |
required | 必填字段名称数组 |
additionalProperties: false | 拒绝未知字段(安全性) |
minProperties: 1 | 用于更新架构——至少要求一个字段 |
常见验证关键字
字符串验证:
{
"type": "string",
"minLength": 1,
"maxLength": 255,
"pattern": "^[a-z0-9-]+$",
"format": "email"
}
数字验证:
{
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
枚举验证:
{
"type": "string",
"enum": ["draft", "active", "archived"],
"default": "draft"
}
可空字段:
{
"type": ["string", "null"],
"maxLength": 2000
}
可用格式
AJV 搭配 ajv-formats 支持:
email- 电子邮件地址uri- 完整 URIuuid- UUID v4date- ISO 日期(YYYY-MM-DD)date-time- ISO 日期时间time- ISO 时间ipv4、ipv6- IP 地址hostname- 主机名
自定义格式(定义于 validator.js):
phone- E.164 电话号码格式(+1234567890)slug- URL 安全标识符(小写字母、连字符)
使用验证中间件
导入并应用
import { validateBody, validateQuery, validateParams } from './middleware/validate.js';
// Validate request body
app.post('/api/users',
validateBody('entities/user.create'),
createUser
);
// Validate query parameters
app.get('/api/items',
validateQuery('api/list-items'),
listItems
);
// Validate path parameters
app.get('/api/users/:id',
validateParams('common/uuid-param'),
getUser
);
// Combined validation
app.patch('/api/users/:id',
validateParams('common/uuid-param'),
validateBody('entities/user.update'),
updateUser
);
错误响应格式
验证失败时返回:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"path": "/email",
"message": "Invalid email format",
"keyword": "format"
},
{
"path": "/password",
"message": "Must be at least 8 characters",
"keyword": "minLength"
}
]
}
}
状态码:
422- 请求体验证失败400- 查询或参数验证失败
在服务中验证
在中间件之外进行验证时(例如,在写入数据库前):
import { validate } from '../api/middleware/validate.js';
async function createUser(data) {
// Validate before database insert (defense in depth)
validate(data, 'entities/user.create');
// Proceed with insert...
const result = await query(userQueries.create, [data.email, data.name]);
return result.rows[0];
}
查询参数强制转换
查询字符串始终是字符串。中间件会自动强制转换:
| 架构类型 | 输入 | 结果 |
|---|---|---|
integer | "20" | 20 |
boolean | "true" | true |
array | "a,b,c" | ["a", "b", "c"] |
查询架构示例:
{
"$id": "api/list-items",
"type": "object",
"properties": {
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0
},
"status": {
"type": "string",
"enum": ["draft", "active", "archived"]
}
},
"additionalProperties": false
}
生成 TypeScript 类型
根据架构生成 .d.ts 文件,用于 JSDoc 类型检查:
npm run generate:types
工作原理
该脚本使用 json-schema-to-typescript 将 JSON Schema 文件转换为 TypeScript 声明文件:
- 读取
/schemas/中的所有.schema.json文件 - 在
src/types/generated/中生成对应的.d.ts文件 - 可在 JSDoc 注释中导入类型以进行类型检查
生成的结构
src/types/generated/
common/
uuid.d.ts
error-response.d.ts
pagination.d.ts
entities/
user.d.ts
user.create.d.ts
user.update.d.ts
api/
login.d.ts
register.d.ts
使用生成的类型
在 JSDoc 注释中导入类型:
/**
* @typedef {import('./types/generated/entities/user.create').UserCreate} CreateUserInput
* @typedef {import('./types/generated/entities/user').User} User
*/
/**
* Create a new user
* @param {CreateUserInput} data - User creation data
* @returns {Promise<User>} Created user
*/
async function createUser(data) {
validate(data, 'entities/user.create');
// data has full type information from schema
const result = await db.query(userQueries.create, [data.email, data.password, data.name]);
return result.rows[0];
}
重新生成类型
更新架构后,运行 npm run generate:types,以确保类型保持同步。
与数据库约束保持一致
架构验证应与数据库约束相对应:
| 数据库约束 | JSON Schema 等效项 |
|---|---|
NOT NULL | 在 required 数组中列出 |
UNIQUE | 在服务层验证(不在架构中) |
CHECK (status IN ('a', 'b')) | "enum": ["a", "b"] |
VARCHAR(255) | "maxLength": 255 |
CHECK (amount > 0) | "minimum": 1(排他:"exclusiveMinimum": 0) |
OpenAPI 集成
从 OpenAPI 规范引用架构:
# openapi.yaml
paths:
/users:
post:
requestBody:
required: true
content:
application/json:
schema:
$ref: './schemas/entities/user.create.schema.json'
responses:
'201':
content:
application/json:
schema:
$ref: './schemas/entities/user.schema.json'
'422':
$ref: '#/components/responses/ValidationError'
使用 tsc 进行类型检查
项目使用 tsc --checkJs 对带有 JSDoc 注释的 JavaScript 文件进行类型检查。
运行类型检查
npm run typecheck
要求
类型检查需要:
- 运行
npm install安装 @types 包 - 文件必须包含 JSDoc 类型注解
jsconfig.json 配置
{
"compilerOptions": {
"checkJs": true,
"strict": true,
"skipLibCheck": true,
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext"
},
"include": ["src/**/*.js", "test/**/*.js"],
"exclude": ["node_modules"]
}
模板语法处理
包含模板语法({{VARIABLE}})的文件会在项目创建时处理,因此在项目生成前应将其排除在类型检查之外。
在起始模板中:
config/index.js等文件包含{{PROJECT_NAME}}占位符- 模板处理后,这些文件就是有效的 JavaScript
- 在生成的项目中运行
npm install后,类型检查即可正常运行
常见类型模式
// Import Express types
/**
* @typedef {import('express').Request} Request
* @typedef {import('express').Response} Response
* @typedef {import('express').NextFunction} NextFunction
*/
// Type middleware parameters
/**
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
export function myMiddleware(req, res, next) {
// ...
}
// Import schema types (after npm run generate:types)
/**
* @typedef {import('./types/generated/entities/user.create').UserCreate} CreateUserInput
*/
最佳实践
- 单一事实来源 - Schema 定义验证、类型和文档
- 默认严格 - 始终使用
additionalProperties: false - 提供描述性错误消息 - 为每个属性使用
description - 纵深防御 - 在 API 边界和写入数据库前都进行验证
- 与数据库保持一致 - Schema 约束应与 CHECK 约束匹配
- 生成而非重复 - 使用
npm run generate:types生成 TypeScript - 添加 JSDoc 类型 - 所有导出的函数都应标注参数和返回值类型
相关技能
rest-api- API 端点模式和 HTTP 状态码error-handling- 自定义错误类,包括 ValidationErrorforms- 客户端 HTML5 验证(UX 层)security- 输入清理和输出编码typescript-author- TypeScript 模式和 Zod 替代方案
评论
支持 Markdown