class-validator の使い方

Decorator-based property validation for classes.

v0.15.17.4M/週MITバリデーション
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

class-validator の使い方 — デコレータベースのクラスバリデーション

一言でいうと

class-validator は、TypeScript/JavaScript のクラスプロパティにデコレータを付与するだけでバリデーションを実現するライブラリです。内部的に validator.js を利用しており、ブラウザ・Node.js の両方で動作します。

どんな時に使う?

  1. NestJS での API リクエストバリデーション — DTO(Data Transfer Object)クラスにデコレータを付けて、コントローラーに到達する前にリクエストボディを自動検証する
  2. フォーム入力値のサーバーサイドバリデーション — ユーザーから受け取ったデータをクラスインスタンスに変換し、型安全にバリデーションを行う
  3. ドメインモデルの整合性チェック — エンティティやバリューオブジェクトに制約を宣言的に定義し、ビジネスルールの違反を検出する

インストール

# npm
npm install class-validator --save

# yarn
yarn add class-validator

# pnpm
pnpm add class-validator

注意: TypeScript で使用する場合、tsconfig.jsonexperimentalDecoratorsemitDecoratorMetadatatrue に設定してください。

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

基本的な使い方

最も典型的なパターンは、クラスのプロパティにバリデーションデコレータを付与し、validate 関数で検証する方法です。

import {
  validate,
  validateOrReject,
  IsString,
  IsEmail,
  IsInt,
  Length,
  Min,
  Max,
} from 'class-validator';

class CreateUserDto {
  @IsString()
  @Length(2, 50)
  name: string;

  @IsEmail()
  email: string;

  @IsInt()
  @Min(0)
  @Max(150)
  age: number;
}

async function main() {
  const dto = new CreateUserDto();
  dto.name = 'A'; // 2文字未満なのでエラー
  dto.email = 'invalid-email'; // メール形式でないのでエラー
  dto.age = 200; // 150を超えているのでエラー

  const errors = await validate(dto);

  if (errors.length > 0) {
    // errors は ValidationError[] 型
    errors.forEach(err => {
      console.log(`プロパティ: ${err.property}`);
      console.log(`エラー内容:`, err.constraints);
    });
  } else {
    console.log('バリデーション成功');
  }
}

main();

validateOrReject を使えば、エラー時に例外をスローさせることもできます。

async function createUser(dto: CreateUserDto) {
  try {
    await validateOrReject(dto);
    // バリデーション成功 → ビジネスロジックへ
  } catch (errors) {
    // errors は ValidationError[] 型
    console.log('バリデーション失敗:', errors);
  }
}

よく使う API — class-validator の主要機能と使い方

1. 基本的なバリデーションデコレータ

import {
  IsString,
  IsNumber,
  IsBoolean,
  IsDate,
  IsEmail,
  IsUrl,
  IsUUID,
  IsEnum,
  IsOptional,
  IsNotEmpty,
} from 'class-validator';

enum Role {
  ADMIN = 'admin',
  USER = 'user',
}

class UserProfile {
  @IsNotEmpty()
  @IsString()
  name: string;

  @IsEmail()
  email: string;

  @IsUrl()
  @IsOptional() // undefined / null の場合はバリデーションをスキップ
  website?: string;

  @IsEnum(Role)
  role: Role;

  @IsUUID('4')
  id: string;

  @IsDate()
  createdAt: Date;

  @IsBoolean()
  isActive: boolean;
}

2. 文字列・数値の制約デコレータ

import {
  Length,
  MinLength,
  MaxLength,
  Contains,
  Matches,
  IsInt,
  Min,
  Max,
  IsPositive,
  IsNegative,
} from 'class-validator';

class Product {
  @Length(3, 100)
  name: string;

  @MinLength(10)
  @MaxLength(5000)
  description: string;

  @Matches(/^[A-Z]{2}-\d{4}$/, {
    message: 'SKUは「XX-0000」形式で入力してください',
  })
  sku: string;

  @IsInt()
  @IsPositive()
  @Max(999999)
  price: number;

  @IsInt()
  @Min(0)
  stock: number;
}

3. ネストされたオブジェクトのバリデーション

class-transformer と組み合わせて使うのが一般的です。@ValidateNested()@Type() を併用します。

import { ValidateNested, IsString, IsPostalCode } from 'class-validator';
import { Type } from 'class-transformer';

class Address {
  @IsString()
  street: string;

  @IsString()
  city: string;

  @IsPostalCode('JP')
  postalCode: string;
}

class Order {
  @IsString()
  orderId: string;

  @ValidateNested()
  @Type(() => Address) // class-transformer のデコレータ
  shippingAddress: Address;
}

// 使用例
async function validateOrder() {
  const order = new Order();
  order.orderId = 'ORD-001';

  const address = new Address();
  address.street = '';
  address.city = '東京';
  address.postalCode = 'invalid';
  order.shippingAddress = address;

  const errors = await validate(order);
  // shippingAddress.street と shippingAddress.postalCode でエラーが発生
  // errors[0].children にネストされた ValidationError が格納される
}

4. カスタムバリデーションメッセージ

import { IsString, MinLength, ValidationArguments } from 'class-validator';

class Article {
  @IsString({ message: 'タイトルは文字列で入力してください' })
  @MinLength(5, {
    message: (args: ValidationArguments) => {
      return `タイトルは${args.constraints[0]}文字以上必要です(現在: ${args.value?.length ?? 0}文字)`;
    },
  })
  title: string;
}

特殊トークンも利用可能です:

トークン説明
$valueバリデーション対象の値
$propertyプロパティ名
$targetクラス名
$constraint1, $constraint2...デコレータに渡した制約値
class Post {
  @MinLength(10, {
    message: '$propertyは最低$constraint1文字必要です(入力値: $value)',
  })
  title: string;
}

5. カスタムバリデーションデコレータ

プロジェクト固有のバリデーションルールを作成できます。

import {
  registerDecorator,
  ValidationOptions,
  ValidatorConstraint,
  ValidatorConstraintInterface,
  ValidationArguments,
} from 'class-validator';

// 1. バリデーションロジックを定義
@ValidatorConstraint({ async: false })
class IsJapanesePhoneNumberConstraint implements ValidatorConstraintInterface {
  validate(value: any, args: ValidationArguments): boolean {
    if (typeof value !== 'string') return false;
    return /^0\d{1,4}-?\d{1,4}-?\d{4}$/.test(value);
  }

  defaultMessage(args: ValidationArguments): string {
    return `${args.property}は有効な日本の電話番号形式ではありません`;
  }
}

// 2. デコレータファクトリを作成
function IsJapanesePhoneNumber(validationOptions?: ValidationOptions) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      constraints: [],
      validator: IsJapanesePhoneNumberConstraint,
    });
  };
}

// 3. 使用
class ContactForm {
  @IsJapanesePhoneNumber({ message: '正しい電話番号を入力してください' })
  phone: string;
}

バリデーションオプション(ValidatorOptions)

validate の第2引数に渡せるオプションは実用上非常に重要です。

import { validate } from 'class-validator';

const errors = await validate(dto, {
  whitelist: true,              // デコレータのないプロパティを自動除去
  forbidNonWhitelisted: true,   // デコレータのないプロパティがあればエラー
  forbidUnknownValues: true,    // 未知のオブジェクトを拒否(デフォルト: true)
  skipMissingProperties: false, // 欠損プロパティをスキップしない
  groups: ['create'],           // バリデーショングループを指定
  stopAtFirstError: true,       // 最初のエラーで停止
  validationError: {
    target: false,              // エラーオブジェクトに target を含めない(API レスポンス向け)
    value: false,               // エラーオブジェクトに value を含めない
  },
});

重要: forbidUnknownValues はデフォルトで true です。false にすると未知のオブジェクトがバリデーションを通過してしまうため、変更は推奨されません。

バリデーショングループの使い方

作成時と更新時で異なるルールを適用したい場合に便利です。

import { IsNotEmpty, IsOptional, IsString, MinLength, validate } from 'class-validator';

class UpdateUserDto {
  @IsNotEmpty({ groups: ['create'] })
  @IsOptional({ groups: ['update'] })
  @IsString({ always: true }) // always: true で全グループに適用
  @MinLength(2, { always: true })
  name: string;

  @IsNotEmpty({ groups: ['create'] })
  @IsOptional({ groups: ['update'] })
  @IsString({ always: true })
  email: string;
}

// 作成時: name, email ともに必須
const createErrors = await validate(dto, { groups: ['create'] });

// 更新時: name, email は省略可能
const updateErrors = await validate(dto, { groups: ['update'] });

類似パッケージとの比較

特徴class-validatorzodjoiyup
アプローチデコレータベース(クラス)スキーマベース(関数型)スキーマベース(チェーン)スキーマベース(チェーン)
TypeScript 親和性◎(デコレータ前提)◎(型推論が強力)△(別途型定義が必要)
NestJS 統合◎(公式推奨)△(追加設定が必要)
バンドルサイズやや大きい小さい大きい中程度
ランタイム型生成✓(z.infer
非同期バリデーション
カスタムルールデコレータで定義.refine().custom().test()

選定の目安:

  • NestJS を使っている → class-validator(公式推奨、class-transformer との連携が強力)
  • 関数型スタイルが好み / フロントエンド中心 → zod
  • Express + 軽量に使いたい → zod または joi

注意点・Tips

1. プレーンオブジェクトではバリデーションが効かない

// ❌ これではバリデーションされない
const plain = { name: '', email: 'invalid' };
const errors = await validate(plain as any); // エラーは検出されない

// ✅ クラスインスタンスを生成する
import { plainToInstance } from 'class-transformer';

const dto = plainToInstance(CreateUserDto, plain);
const errors = await validate(dto); // 正しくバリデーションされる

これは class-validator がプロトタイプチェーンを参照してデコレータのメタデータを取得するためです。plainToInstance(class-transformer)でインスタンス化するのが定番パターンです。

2. whitelist: true は本番環境で必須

// 悪意のあるリクエストボディ
// { "name": "Alice", "isAdmin": true }

// whitelist: true なら isAdmin は自動的に除去される
const errors = await validate(dto, { whitelist: true });

デコレータが付いていないプロパティを自動除去することで、マスアサインメント攻撃を防げます。

3. API レスポンスに target を含めない

// target にはバリデーション対象のオブジェクト全体が含まれる
// パスワードなどの機密情報が漏洩する可能性がある
const errors = await validate(dto, {
  validationError: { target: false },
});

4. 配列の