关于 class-validator
面向 TypeScript 后端开发者的 class-validator 参考技能,介绍基于装饰器的 DTO 校验,并包含 NestJS 集成及常见配置问题。
主要功能
- 01字符串、数字、日期等校验
- 02嵌套对象与数组校验
- 03NestJS ValidationPipe 集成
- 04自定义校验器基础示例
- 05装饰器配置与故障排查
适用场景
- 01校验 API 请求 DTO
- 02在 NestJS 中过滤未知字段
- 03检查嵌套对象和数组数据
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill class-validator安装后在 Claude Code 中输入/class-validator确认已加载。
附带文件advanced.md
详细介绍
Class-validator - 快速参考
完整参考:自定义验证器、跨字段验证、验证组、错误格式化和 class-transformer 集成,请参阅 advanced.md。
深入了解:使用
mcp__documentation__fetch_docs,并将 technology 设为:class-validator,获取全面的文档。
设置
npm install class-validator class-transformer
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
基本用法
import {
validate,
IsString,
IsEmail,
IsInt,
Min,
Max,
Length,
IsOptional,
} from 'class-validator';
import { plainToInstance } from 'class-transformer';
class CreateUserDto {
@IsString()
@Length(2, 50)
name: string;
@IsEmail()
email: string;
@IsInt()
@Min(18)
@Max(120)
age: number;
@IsOptional()
@IsString()
bio?: string;
}
// Validate
async function validateUser(data: unknown) {
const user = plainToInstance(CreateUserDto, data);
const errors = await validate(user);
if (errors.length > 0) {
throw new Error(errors.map((e) => Object.values(e.constraints || {})).flat().join(', '));
}
return user;
}
常用装饰器
字符串验证器
import {
IsString, IsNotEmpty, Length, MinLength, MaxLength,
Matches, IsUUID, IsEmail, IsUrl, IsIP,
} from 'class-validator';
class StringValidationDto {
@IsString()
@IsNotEmpty()
required: string;
@Length(5, 20)
username: string;
@MinLength(8)
@Matches(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/, {
message: 'Password must contain letters and numbers',
})
password: string;
@IsEmail()
email: string;
@IsUrl({ require_protocol: true })
website: string;
@IsUUID('4')
id: string;
}
数字验证器
import { IsNumber, IsInt, IsPositive, Min, Max, IsDivisibleBy } from 'class-validator';
class NumberValidationDto {
@IsNumber()
decimal: number;
@IsInt()
integer: number;
@IsPositive()
positive: number;
@Min(0)
@Max(100)
percentage: number;
}
日期验证器
import { IsDate, MinDate, MaxDate, IsISO8601 } from 'class-validator';
import { Type } from 'class-transformer';
class DateValidationDto {
@IsDate()
@Type(() => Date)
date: Date;
@MinDate(new Date('2020-01-01'))
@Type(() => Date)
afterDate: Date;
@IsISO8601()
isoString: string;
}
数组验证器
import {
IsArray, ArrayMinSize, ArrayMaxSize, ArrayUnique,
ArrayNotEmpty, ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
class ItemDto {
@IsString()
name: string;
}
class ArrayValidationDto {
@IsArray()
@ArrayNotEmpty()
@ArrayMinSize(1)
@ArrayMaxSize(10)
@IsString({ each: true }) // Validate each item
tags: string[];
@IsArray()
@ValidateNested({ each: true })
@Type(() => ItemDto)
items: ItemDto[];
}
嵌套对象验证
import { IsObject, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
class AddressDto {
@IsString()
street: string;
@IsString()
city: string;
}
class UserWithAddressDto {
@IsString()
name: string;
@ValidateNested()
@Type(() => AddressDto)
address: AddressDto;
}
其他验证器
import {
IsBoolean, IsEnum, IsIn, IsOptional,
IsCreditCard, IsPhoneNumber,
} from 'class-validator';
enum UserRole {
ADMIN = 'admin',
USER = 'user',
GUEST = 'guest',
}
class MixedValidationDto {
@IsBoolean()
active: boolean;
@IsEnum(UserRole)
role: UserRole;
@IsIn(['draft', 'published', 'archived'])
status: string;
@IsOptional()
@IsCreditCard()
creditCard?: string;
}
NestJS 集成
// main.ts
import { ValidationPipe } from '@nestjs/common';
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Strip non-decorated properties
forbidNonWhitelisted: true, // Throw on unknown properties
transform: true, // Auto-transform payloads to DTO instances
transformOptions: {
enableImplicitConversion: true,
},
})
);
// users.controller.ts
@Controller('users')
export class UsersController {
@Post()
create(@Body() createUserDto: CreateUserDto) {
// createUserDto is already validated and transformed
return this.usersService.create(createUserDto);
}
@Get()
findAll(@Query() query: PaginationQueryDto) {
// Query params also validated
return this.usersService.findAll(query);
}
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.findOne(id);
}
}
部分 DTO(用于更新)
import { PartialType, OmitType, PickType, IntersectionType } from '@nestjs/mapped-types';
// All fields optional
export class UpdateUserDto extends PartialType(CreateUserDto) {}
// Omit specific fields
export class CreateUserWithoutPasswordDto extends OmitType(CreateUserDto, ['password']) {}
// Pick specific fields
export class LoginDto extends PickType(CreateUserDto, ['email', 'password']) {}
// Combine DTOs
export class ExtendedUserDto extends IntersectionType(CreateUserDto, AdditionalFieldsDto) {}
自定义验证器(基础)
import {
registerDecorator,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from 'class-validator';
@ValidatorConstraint({ async: false })
export class IsStrongPasswordConstraint implements ValidatorConstraintInterface {
validate(password: string, args: ValidationArguments) {
const hasUppercase = /[A-Z]/.test(password);
const hasLowercase = /[a-z]/.test(password);
const hasNumber = /\d/.test(password);
const minLength = password.length >= 8;
return hasUppercase && hasLowercase && hasNumber && minLength;
}
defaultMessage(args: ValidationArguments) {
return 'Password must contain uppercase, lowercase, and number';
}
}
export function IsStrongPassword(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [],
validator: IsStrongPasswordConstraint,
});
};
}
// Usage
class RegisterDto {
@IsStrongPassword()
password: string;
}
不适用此技能的场景
- 客户端验证 - 使用 Zod 和 React Hook Form
- 非 NestJS 后端 - Zod 与框架的耦合更少
- 函数式编程模式 - 装饰器需要类
- 简单验证 - Zod 的 API 更简单
对比:class-validator 与 Zod
| 特性 | class-validator | Zod |
|---|---|---|
| 风格 | 装饰器 | 函数式 |
| 框架 | NestJS 标准 | 与框架无关 |
| 转换 | class-transformer | 内置 |
| 包体积 | 更大 | 更小 |
| 类型推断 | 手动 | 自动 |
反模式
| 反模式 | 不良之处 | 正确做法 |
|---|---|---|
| 缺少 @Type() 装饰器 | 嵌套验证会失败 | 对嵌套对象使用 @Type() |
| ValidationPipe 中未启用 whitelist | 会接受未知属性 | 设置 whitelist: true |
| 在 DTO 中使用 any | 绕过验证 | 使用适当的类型 |
| 在控制器中手动验证 | 代码重复 | 全局使用 ValidationPipe |
| 缺少 experimentalDecorators | 装饰器无法工作 | 在 tsconfig.json 中启用 |
| 未使用 class-transformer | 不会验证普通对象 | 使用 plainToInstance() |
快速故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 装饰器无法工作 | tsconfig 配置错误 | 启用 experimentalDecorators: true |
| 嵌套验证失败 | 缺少 @Type() | 添加 @Type(() => NestedClass) |
| 未运行验证 | 未设置 ValidationPipe | 在 main.ts 中全局添加 ValidationPipe |
| 接受未知属性 | 未启用 whitelist | 在 ValidationPipe 中设置 whitelist: true |
| 转换不起作用 | 缺少 transform 选项 | 在 ValidationPipe 中设置 transform: true |
| 异步验证器失败 | 设置错误 | 在 @ValidatorConstraint 中使用 async: true |
检查清单
- 在 tsconfig 中启用装饰器
- 使用 class-transformer 进行嵌套验证/转换
- 在 NestJS 中全局设置 ValidationPipe
- 设置 whitelist 和 forbidNonWhitelisted
- 针对特定逻辑编写自定义验证器
- 为 API 响应格式化错误
参考
深入了解:使用
mcp__documentation__fetch_docs,并将 technology 设为:class-validator
评论
支持 Markdown