01 express-validator 基础与执行流程
1. 学习目标
- 理解验证链为什么是 Express 中间件;
- 校验
body、query、params、headers和cookies; - 使用
validationResult()读取错误; - 理解解析、校验、Controller 的执行顺序;
- 完成第一个 TypeScript 校验接口。
2. 安装与导入
npm install express-validator
express-validator 自带 TypeScript 类型,不需要安装 @types/express-validator。
import {
body,
param,
query,
validationResult,
} from "express-validator";
3. 第一个接口
import express, {
type NextFunction,
type Request,
type Response,
} from "express";
import {
body,
validationResult,
} from "express-validator";
const app = express();
app.post(
"/wishes",
express.json(),
body("name")
.isString()
.withMessage("name 必须是字符串")
.bail()
.trim()
.notEmpty()
.withMessage("name 不能为空"),
body("content")
.isString()
.withMessage("content 必须是字符串")
.bail()
.trim()
.isLength({ min: 1, max: 200 })
.withMessage("content 必须是 1 到 200 个字符"),
(req: Request, res: Response): void => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
res.status(201).json({ data: req.body });
},
);
执行顺序是:
express.json()
→ name 校验链
→ content 校验链
→ 路由处理函数
ValidationChain 本身是中间件,但它只把错误记录到请求上下文,不会自动返回 400。
4. 为什么 express.json() 必须在 body 校验前
body("name") 从 req.body 读取字段。JSON 请求体尚未解析时,验证器无法获得正确数据:
// 正确
app.post(
"/wishes",
express.json(),
body("name").isString(),
handler,
);
通常也可以在应用级安装:
app.use(express.json());
然后路由中不必重复添加。
5. 校验不同位置
请求体
body("email").isEmail();
路径参数
param("id")
.isInt({ min: 1 })
.withMessage("id 必须是正整数")
.toInt();
查询参数
query("page")
.optional()
.isInt({ min: 1 })
.withMessage("page 必须是正整数")
.toInt();
请求头
import { header } from "express-validator";
header("x-request-id")
.optional()
.isUUID();
Cookie
import { cookie } from "express-validator";
cookie("sessionId")
.isString()
.notEmpty();
读取 Cookie 之前还需要相应的 Cookie 解析中间件。
check()
import { check } from "express-validator";
check("keyword").isString();
check() 会从多个请求位置寻找字段。日常接口更建议使用 body()、query()、param() 等明确位置,防止同名字段来源不清。
6. 抽取统一错误中间件
import {
validationResult,
type ValidationError,
type Result,
} from "express-validator";
import type {
NextFunction,
Request,
Response,
} from "express";
export function validateRequest(
req: Request,
res: Response,
next: NextFunction,
): void {
const errors: Result<ValidationError> = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
next();
}
路由变得更清晰:
router.post(
"/create",
express.json(),
createWishValidation,
validateRequest,
createWish,
);
7. TypeScript 为什么不能代替它
interface CreateWishBody {
name: string;
content: string;
}
function handler(
req: Request<object, object, CreateWishBody>,
res: Response,
): void {
// TypeScript 相信 name 是 string,客户端却仍可发送数字。
}
Request 泛型只约束代码编写,不会生成运行时校验。正确顺序是:
unknown HTTP 输入
→ 运行时校验
→ 提取可信 DTO
→ TypeScript 业务代码
8. HTTP 状态码
格式、类型、必填字段错误通常返回:
400 Bad Request
团队也可能使用 422 Unprocessable Content,但应统一约定。认证失败、权限不足、资源不存在和唯一键冲突不应全部伪装成校验错误。
9. 常见错误
- 忘记读取
validationResult(req); - 校验器放在 Controller 后面;
express.json()放在 body 校验后面;- 校验失败响应后仍然调用
next(); - 直接把
errors.array()中的密码原值返回; - 认为 TypeScript Request 泛型已经检查客户端输入。
10. 练习题
- 为
GET /wishes/:id添加正整数路径参数校验,并转换为 number。 - 为列表接口添加可选的
page、size参数,其中默认值分别为 1 和 20。 - 校验
Authorization请求头必须以Bearer开头。 - 故意把
express.json()放在 body 校验之后,观察错误结果,再恢复正确顺序。 - 把两个接口中重复的错误处理抽取成
validateRequest中间件。