01 express-validator 基础与执行流程

1. 学习目标

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. 常见错误

10. 练习题

  1. GET /wishes/:id 添加正整数路径参数校验,并转换为 number。
  2. 为列表接口添加可选的 pagesize 参数,其中默认值分别为 1 和 20。
  3. 校验 Authorization 请求头必须以 Bearer 开头。
  4. 故意把 express.json() 放在 body 校验之后,观察错误结果,再恢复正确顺序。
  5. 把两个接口中重复的错误处理抽取成 validateRequest 中间件。

官方参考