Zod vs class-validator — Evaluation
评估日期:2026-07-29 状态:暂不迁移,待触发条件满足后重新评估(见 ADR-0016) 2026-11-04 订正:下表有两行事实已过期(
NestJS 集成与当前代码库),就地标注、原文保留; 订正内容见文末「订正记录(2026-11-04)」。结论(暂不迁移)与触发条件都没有变。
对比
| 维度 | Zod | class-validator(当前) |
|---|---|---|
| 类型推断 | ✅ z.infer<typeof schema> 自动推导 | ❌ 需手动写 interface |
| 前端可用 | ✅ 零依赖,轻量 | ❌ 依赖 reflect-metadata + decorators |
| NestJS 集成 | 需自定义 Pipe | ✅ ValidationPipe 原生支持 |
| Swagger 文档 | 需额外配置 | ✅ 装饰器内联 Swagger |
| 前端表单验证 | ✅ VeeValidate + Zod 原生集成 | ❌ 需单独写验证规则 |
| 当前代码库 | 0 使用 | ~100+ DTO 类,6 个子系统,30+ 装饰器文件 |
| 学习成本 | 中(团队需学习 Zod DSL) | 低(团队已熟悉) |
| 运行时性能 | 中等 | 中等 |
不迁移的原因
- 装饰器体系已成熟:
WalnutAdminDecoratorField*将 validation + transformation + Swagger docs 统一到一个装饰器中,实现了类似 Zod "单一声明"的体验 - 迁移成本太高:涉及 16 个 guard、中件间/interceptor 层、
libs/decorators/6 个子系统、~100+ DTO 类 - 前端表单验证复用不是当前痛点:要发挥 Zod 的前后端复用优势,需要前端接入 VeeValidate + Zod,目前没有这个需求
- 避免双范式并存:逐步迁移意味着长期维护 class-validator 和 Zod 两套体系
触发条件(何时重新评估)
| 触发条件 | 说明 |
|---|---|
| 前端接入 VeeValidate + Zod | DRY 验证变为可实现 |
| 新建 NestJS 绿场模块 | 可在新模块试点 Zod,无需迁移旧代码 |
class-validator 停止维护或不兼容 NestJS 新版本 | 强制迁移 |
| 3+ 个前端应用共享同一后端 | 共享 schema 的 ROI 提高 |
订正记录(2026-11-04)
上表有两行事实性陈述已经过期,就地标注、原文保留(判断本身没被推翻 —— 结论仍是「暂不迁移」, 变的是那两条成本/现状的依据):
- 「NestJS 集成:需自定义 Pipe」不再成立。 NestJS 12 原生支持 Standard Schema (
@Body({ schema })+StandardSchemaValidationPipe,同一份 schema 还能驱动 OpenAPI), 接 Zod 不需要自己写 pipe。也就是说原判断里「Zod 要多一层胶水」这条成本已经消失,剩下的成本是 「存量 DTO 迁移 + 长期双范式并存」—— 正是本页「不迁移的原因」第 2 / 4 条,结论不受影响。 - 「当前代码库:0 使用」不再成立。 admin 已经在用 zod 4:依赖在 catalog 里 (
pnpm-workspace.yaml的zod一行,admin 侧package.json以catalog:引用), 实际消费者是构建期环境变量校验(apps/admin/build/vite/config/env.{development,stage,prod}.ts写import { z } from 'zod/v4',同目录shared.ts用from 'zod';都是 zod 4 的 API)。 准确现状是**「前端构建配置在用、后端 DTO 仍未用」,不是「全仓 0 使用」;两者的区别正是 「前后端共享 schema」这条触发条件的现状 —— 前端还没把 zod 用到表单/接口契约**上。
订正只覆盖这两条事实。触发条件表与「不迁移的原因」按现状仍然成立,重新评估时以这一节为准, 别再引用上表那两个格子里的旧值。