class-validator について
TypeScriptのバックエンド開発者向けclass-validatorリファレンススキルです。デコレーターによるDTO検証、NestJSとの連携、設定時の一般的な問題を扱います。
主な機能
- 01文字列・数値・日付の検証
- 02ネストしたオブジェクトと配列の検証
- 03NestJS ValidationPipeとの連携
- 04カスタムバリデーターの基本例
- 05デコレーター設定とトラブル対応
活用シーン
- 01APIリクエストDTOの検証
- 02NestJSでの未知フィールドの除外
- 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 を参照してください。
詳細情報:包括的なドキュメントを確認するには、technology:
class-validatorを指定してmcp__documentation__fetch_docsを使用してください。
セットアップ
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;
}
このスキルを使用しない場合
- クライアント側のバリデーション - React Hook Form と Zod を使用します
- 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 レスポンス用のエラーフォーマット
参考資料
詳細情報:technology:
class-validatorを指定してmcp__documentation__fetch_docsを使用してください
コメント
Markdown 対応