Nest.js同DTO适配POST/PATCH如何用class-validator做条件校验
class-validator 实现POST/PATCH复用DTO差异化校验方案
不用在业务代码里手写校验逻辑,有两种成熟的落地方式,根据业务场景选就行:
方案1:Nest官方映射类型(最简便,适合全字段可选的更新场景)
这是Nest生态原生支持的能力,不需要额外配置,核心是用@nestjs/mapped-types提供的PartialType方法,自动把创建DTO的所有字段转为可选,同时继承全部原有校验规则。
- 先写POST请求用的创建DTO,所有必填字段正常配置校验规则,不需要加
@IsOptional
// create-user.dto.ts import { IsString, IsEmail, MinLength, IsOptional } from 'class-validator'; export class CreateUserDto { @IsEmail() email: string; @IsString() @MinLength(6) password: string; @IsString() nickname: string; // 创建时本身就可选的字段正常加@IsOptional即可 @IsString() @IsOptional() avatar?: string; }
- 新增更新DTO,直接继承
PartialType包装后的创建DTO即可,不需要写任何重复字段定义
// update-user.dto.ts import { PartialType } from '@nestjs/mapped-types'; import { CreateUserDto } from './create-user.dto'; export class UpdateUserDto extends PartialType(CreateUserDto) {}
- 路由层对应使用即可:POST用
CreateUserDto,PATCH用UpdateUserDto,所有校验规则自动生效,PATCH请求中缺失的字段不会触发必填校验。
方案2:class-validator分组校验(最灵活,适合复杂校验场景)
如果你的PATCH请求不是全字段可选(比如更新时要求必须传用户状态字段),或者同个字段在创建、更新时校验规则不同(比如创建密码最低6位,更新密码最低8位),就用class-validator原生的分组能力,完全可以做到两个请求复用同一个DTO类。
- 先定义校验分组枚举,避免魔法字符串
enum DtoGroups { CREATE = 'create', UPDATE = 'update', }
- 编写通用DTO,给每个校验规则指定生效的分组,同时给仅在更新时可选的字段,单独给
@IsOptional指定UPDATE分组
import { IsString, IsEmail, IsOptional, MinLength, IsNumber } from 'class-validator'; export class UserDto { // 邮箱格式校验两个场景都生效,仅更新时可选 @IsEmail({}, { groups: [DtoGroups.CREATE, DtoGroups.UPDATE] }) @IsOptional({ groups: [DtoGroups.UPDATE] }) email: string; // 密码长度校验两个场景生效,仅更新时可选,也可以单独给更新场景配置不同的长度规则 @IsString({ groups: [DtoGroups.CREATE, DtoGroups.UPDATE] }) @MinLength(6, { groups: [DtoGroups.CREATE] }) @MinLength(8, { groups: [DtoGroups.UPDATE] }) @IsOptional({ groups: [DtoGroups.UPDATE] }) password: string; @IsString({ groups: [DtoGroups.CREATE, DtoGroups.UPDATE] }) @IsOptional({ groups: [DtoGroups.UPDATE] }) nickname: string; // 头像两个场景都可选 @IsString({ groups: [DtoGroups.CREATE, DtoGroups.UPDATE] }) @IsOptional({ groups: [DtoGroups.CREATE, DtoGroups.UPDATE] }) avatar?: string; // 比如用户状态,创建、更新都必填,就不要加@IsOptional @IsNumber({}, { groups: [DtoGroups.CREATE, DtoGroups.UPDATE] }) status: number; }
- 路由层使用时,给ValidationPipe传入当前请求对应的分组即可,不需要拆分DTO
@Post() create(@Body(new ValidationPipe({ groups: [DtoGroups.CREATE] })) userDto: UserDto) { return this.userService.create(userDto); } @Patch(':id') update( @Param('id') id: string, @Body(new ValidationPipe({ groups: [DtoGroups.UPDATE] })) userDto: UserDto ) { return this.userService.update(id, userDto); }
如果觉得每个路由都重复写ValidationPipe配置太麻烦,可以封装自定义参数装饰器,把分组配置内置,简化代码。
注意避坑
- 给校验装饰器配置
groups属性后,该规则只会在指定分组下生效,所以公共校验规则(比如字段类型、格式校验)一定要把CREATE、UPDATE两个分组都加上,避免出现更新时校验失效的问题 - 不要用「全字段加@IsOptional+业务层手写校验」的方案,维护成本极高,字段调整时需要同步修改多处校验逻辑,容易出漏子
内容的提问来源于stack exchange,提问作者Marumba
相关产品推荐
相关产品推荐

