NestJSにおけるPipeは、リクエストデータの変換と検証を担う重要なコンポーネントです。適切なPipeを活用することで、Controllerに到達する前に不正なデータを排除し、型安全で堅牢なAPIを構築できます。本記事では、組み込みPipeの活用方法からclass-validatorを使ったDTO検証、さらにカスタムPipeの作成まで、実践的なバリデーション手法を解説します。

実行環境と前提条件

本記事の内容を実践するにあたり、以下の環境を前提としています。

項目 バージョン・要件
Node.js 20以上
npm 10以上
NestJS 11.x
class-validator 0.14.x
class-transformer 0.5.x
OS Windows / macOS / Linux
エディタ VS Code(推奨)

事前に以下の準備を完了してください。

  • NestJS CLIのインストール済み
  • NestJSプロジェクトの作成済み
  • 基本的なController・Serviceの理解

NestJSプロジェクトの作成方法はNestJS入門記事、REST APIエンドポイントの実装はREST API開発記事を参照してください。

Pipeとは何か

Pipeは、NestJSのリクエスト処理パイプラインにおいて、Controllerのルートハンドラが実行される直前に呼び出されるコンポーネントです。主に2つの役割を担います。

flowchart LR
    A[クライアント] --> B[リクエスト]
    B --> C[Middleware]
    C --> D[Guard]
    D --> E[Pipe]
    E --> F[Controller]
    F --> G[レスポンス]
    G --> A
    
    style E fill:#4CAF50,color:#fff

Pipeの2つの役割

役割 説明
変換(Transformation) 入力データを目的の形式に変換する 文字列を数値に変換、日付文字列をDateオブジェクトに変換
検証(Validation) 入力データを評価し、有効であればそのまま通過させ、無効であれば例外をスローする 必須フィールドの存在確認、メールアドレス形式の検証

Pipeは例外ゾーン内で実行されるため、Pipe内でスローされた例外は例外フィルター(Exception Filter)で処理されます。つまり、Pipe内で例外が発生すると、Controllerのメソッドは実行されません。

組み込みPipeの活用

NestJSは、一般的なユースケースに対応する組み込みPipeを@nestjs/commonパッケージで提供しています。

利用可能な組み込みPipe一覧

Pipe名 用途 変換元 → 変換先
ValidationPipe DTOベースのバリデーション オブジェクト → 検証済みオブジェクト
ParseIntPipe 整数への変換 文字列 → 整数
ParseFloatPipe 浮動小数点への変換 文字列 → 浮動小数点数
ParseBoolPipe 真偽値への変換 文字列 → boolean
ParseArrayPipe 配列への変換・検証 文字列 → 配列
ParseUUIDPipe UUID形式の検証 文字列 → UUID形式の文字列
ParseEnumPipe Enum値への変換 文字列 → Enum値
ParseDatePipe 日付への変換 文字列 → Date
DefaultValuePipe デフォルト値の設定 undefined/null → デフォルト値
ParseFilePipe ファイルの検証 ファイル → 検証済みファイル

ParseIntPipeによる数値変換

ルートパラメータやクエリパラメータはすべて文字列として受け取られます。ParseIntPipeを使用すると、自動的に整数に変換できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common';
import { ArticlesService } from './articles.service';

@Controller('articles')
export class ArticlesController {
  constructor(private readonly articlesService: ArticlesService) {}

  // GET /articles/123 → idは数値型として受け取る
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    console.log(typeof id); // 'number'
    return this.articlesService.findOne(id);
  }

  // GET /articles?page=2&limit=10
  @Get()
  findAll(
    @Query('page', ParseIntPipe) page: number,
    @Query('limit', ParseIntPipe) limit: number,
  ) {
    return this.articlesService.findAll({ page, limit });
  }
}

不正な値(例:/articles/abc)を受け取った場合、NestJSは自動的に以下のエラーレスポンスを返します。

1
2
3
4
5
{
  "statusCode": 400,
  "message": "Validation failed (numeric string is expected)",
  "error": "Bad Request"
}

ParseIntPipeのカスタマイズ

エラー時のHTTPステータスコードをカスタマイズしたい場合は、インスタンスを直接渡します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { Controller, Get, Param, ParseIntPipe, HttpStatus } from '@nestjs/common';

@Controller('articles')
export class ArticlesController {
  @Get(':id')
  findOne(
    @Param('id', new ParseIntPipe({ 
      errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE 
    }))
    id: number,
  ) {
    return this.articlesService.findOne(id);
  }
}

ParseUUIDPipeによるUUID検証

UUID形式のパラメータを検証する場合はParseUUIDPipeを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { Controller, Get, Param, ParseUUIDPipe } from '@nestjs/common';

@Controller('users')
export class UsersController {
  // UUID v4形式のみを許可
  @Get(':uuid')
  findOne(
    @Param('uuid', new ParseUUIDPipe({ version: '4' }))
    uuid: string,
  ) {
    return this.usersService.findOne(uuid);
  }
}

DefaultValuePipeによるデフォルト値設定

オプショナルなクエリパラメータにデフォルト値を設定する場合は、DefaultValuePipeを他のPipeと組み合わせて使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { 
  Controller, 
  Get, 
  Query, 
  DefaultValuePipe, 
  ParseIntPipe,
  ParseBoolPipe 
} from '@nestjs/common';

@Controller('articles')
export class ArticlesController {
  // GET /articles?page=1&limit=10&published=true
  // パラメータが省略された場合はデフォルト値を使用
  @Get()
  findAll(
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
    @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
    @Query('published', new DefaultValuePipe(true), ParseBoolPipe) published: boolean,
  ) {
    return this.articlesService.findAll({ page, limit, published });
  }
}

class-validatorによるDTOバリデーション

複雑なリクエストボディを検証する場合、class-validatorとclass-transformerを使用したDTOベースのバリデーションが効果的です。

パッケージのインストール

1
npm install class-validator class-transformer

ValidationPipeのグローバル設定

main.tsでValidationPipeをグローバルに設定することで、すべてのエンドポイントで自動的にバリデーションが適用されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,              // DTOに定義されていないプロパティを除去
      forbidNonWhitelisted: true,   // 未定義プロパティがあればエラー
      transform: true,              // 自動型変換を有効化
      transformOptions: {
        enableImplicitConversion: true,  // 暗黙的な型変換を許可
      },
    }),
  );
  
  await app.listen(3000);
}
bootstrap();

ValidationPipeの主要オプション

オプション 説明
whitelist boolean DTOに定義されていないプロパティを自動的に除去
forbidNonWhitelisted boolean 未定義プロパティがあればエラーをスロー(whitelistと併用)
transform boolean プレーンオブジェクトをDTOクラスのインスタンスに変換
disableErrorMessages boolean エラーメッセージの詳細を無効化(本番環境向け)
skipMissingProperties boolean 未定義プロパティの検証をスキップ(PATCH向け)
forbidUnknownValues boolean 不明な値の検証失敗(デフォルト: true)
stopAtFirstError boolean 最初のエラーで検証を停止

DTOクラスの定義

class-validatorが提供するデコレータを使用して、バリデーションルールを定義します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// src/articles/dto/create-article.dto.ts
import {
  IsString,
  IsNotEmpty,
  IsOptional,
  IsArray,
  IsBoolean,
  IsInt,
  Min,
  Max,
  MinLength,
  MaxLength,
  IsEmail,
  IsUrl,
  ArrayMinSize,
  ArrayMaxSize,
  ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';

export class AuthorDto {
  @IsString()
  @IsNotEmpty()
  name: string;

  @IsEmail()
  email: string;
}

export class CreateArticleDto {
  @IsString({ message: 'タイトルは文字列で入力してください' })
  @IsNotEmpty({ message: 'タイトルは必須です' })
  @MinLength(5, { message: 'タイトルは5文字以上で入力してください' })
  @MaxLength(100, { message: 'タイトルは100文字以内で入力してください' })
  title: string;

  @IsString()
  @IsNotEmpty()
  @MinLength(50)
  content: string;

  @IsArray()
  @ArrayMinSize(1, { message: '少なくとも1つのタグが必要です' })
  @ArrayMaxSize(5, { message: 'タグは5つまでです' })
  @IsString({ each: true })  // 配列の各要素を検証
  tags: string[];

  @IsOptional()
  @IsBoolean()
  published?: boolean;

  @IsOptional()
  @IsInt()
  @Min(1)
  @Max(100)
  priority?: number;

  @IsOptional()
  @IsUrl()
  thumbnailUrl?: string;

  @ValidateNested()  // ネストしたオブジェクトを検証
  @Type(() => AuthorDto)  // class-transformerで型変換
  author: AuthorDto;
}

主要なバリデーションデコレータ一覧

カテゴリ デコレータ 説明
存在検証 @IsDefined() undefined/null以外であること
存在検証 @IsOptional() 値がなければ他の検証をスキップ
存在検証 @IsNotEmpty() 空文字・null・undefined以外であること
型検証 @IsString() 文字列であること
型検証 @IsNumber() 数値であること
型検証 @IsInt() 整数であること
型検証 @IsBoolean() 真偽値であること
型検証 @IsArray() 配列であること
型検証 @IsDate() Date型であること
型検証 @IsEnum(enum) 指定したEnumの値であること
文字列検証 @MinLength(n) 最小文字数
文字列検証 @MaxLength(n) 最大文字数
文字列検証 @Length(min, max) 文字数範囲
文字列検証 @Matches(regex) 正規表現にマッチすること
文字列検証 @IsEmail() メールアドレス形式
文字列検証 @IsUrl() URL形式
文字列検証 @IsUUID(version?) UUID形式
数値検証 @Min(n) 最小値
数値検証 @Max(n) 最大値
数値検証 @IsPositive() 正の数
数値検証 @IsNegative() 負の数
配列検証 @ArrayMinSize(n) 最小要素数
配列検証 @ArrayMaxSize(n) 最大要素数
配列検証 @ArrayNotEmpty() 空でないこと
配列検証 @ArrayUnique() 重複要素がないこと
ネスト検証 @ValidateNested() ネストオブジェクトも検証
条件付き検証 @ValidateIf(condition) 条件を満たす場合のみ検証

Controllerでの使用例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/articles/articles.controller.ts
import { Controller, Post, Body, Put, Param, ParseIntPipe } from '@nestjs/common';
import { ArticlesService } from './articles.service';
import { CreateArticleDto } from './dto/create-article.dto';
import { UpdateArticleDto } from './dto/update-article.dto';

@Controller('articles')
export class ArticlesController {
  constructor(private readonly articlesService: ArticlesService) {}

  @Post()
  create(@Body() createArticleDto: CreateArticleDto) {
    // この時点でcreateArticleDtoは検証済み
    return this.articlesService.create(createArticleDto);
  }

  @Put(':id')
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() updateArticleDto: UpdateArticleDto,
  ) {
    return this.articlesService.update(id, updateArticleDto);
  }
}

エラーレスポンスの形式

バリデーションエラーが発生すると、以下の形式でレスポンスが返されます。

1
2
3
4
5
6
7
8
9
{
  "statusCode": 400,
  "message": [
    "タイトルは5文字以上で入力してください",
    "少なくとも1つのタグが必要です",
    "author.email must be an email"
  ],
  "error": "Bad Request"
}

Mapped Typesによる更新DTOの作成

NestJSの@nestjs/mapped-typesパッケージを使用すると、既存のDTOを元に派生DTOを効率的に作成できます。

パッケージのインストール

1
npm install @nestjs/mapped-types

PartialTypeによる部分更新DTO

すべてのプロパティをオプショナルにした更新用DTOを作成します。

1
2
3
4
5
// src/articles/dto/update-article.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateArticleDto } from './create-article.dto';

export class UpdateArticleDto extends PartialType(CreateArticleDto) {}

PartialTypeを使用すると、CreateArticleDtoのすべてのプロパティがオプショナルになり、PATCH操作に適したDTOが作成されます。

その他のMapped Types

ユーティリティ 説明 使用例
PartialType(T) すべてのプロパティをオプショナルに 更新DTO
PickType(T, keys) 指定したプロパティのみを抽出 特定フィールドのみ更新
OmitType(T, keys) 指定したプロパティを除外 ID除外の作成DTO
IntersectionType(A, B) 2つの型を結合 複数DTOの統合
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { PickType, OmitType, IntersectionType } from '@nestjs/mapped-types';
import { CreateArticleDto } from './create-article.dto';

// titleとcontentのみを持つDTO
export class UpdateTitleContentDto extends PickType(CreateArticleDto, [
  'title',
  'content',
] as const) {}

// authorを除いたDTO
export class CreateArticleWithoutAuthorDto extends OmitType(CreateArticleDto, [
  'author',
] as const) {}

// 複数型の結合
class AdditionalFields {
  @IsString()
  externalId: string;
}

export class ExtendedArticleDto extends IntersectionType(
  CreateArticleDto,
  AdditionalFields,
) {}

配列のバリデーション

配列形式のリクエストボディを検証する場合、ParseArrayPipeを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { Controller, Post, Body, ParseArrayPipe } from '@nestjs/common';
import { CreateArticleDto } from './dto/create-article.dto';

@Controller('articles')
export class ArticlesController {
  // POST /articles/bulk - 複数記事の一括作成
  @Post('bulk')
  createBulk(
    @Body(new ParseArrayPipe({ items: CreateArticleDto }))
    createArticleDtos: CreateArticleDto[],
  ) {
    return this.articlesService.createMany(createArticleDtos);
  }
}

クエリパラメータのカンマ区切り配列を解析する場合も同様です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Controller('articles')
export class ArticlesController {
  // GET /articles?ids=1,2,3,4,5
  @Get()
  findByIds(
    @Query('ids', new ParseArrayPipe({ items: Number, separator: ',' }))
    ids: number[],
  ) {
    return this.articlesService.findByIds(ids);
  }
}

カスタムPipeの作成

標準のPipeでは対応できない検証や変換が必要な場合、カスタムPipeを作成します。

PipeTransformインターフェース

すべてのPipeはPipeTransformインターフェースを実装する必要があります。

1
2
3
4
5
6
7
8
9
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class CustomPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    // valueを変換または検証してreturn
    return value;
  }
}

ArgumentMetadataには以下の情報が含まれます。

プロパティ 説明
type パラメータの種類(bodyqueryparamcustom
metatype パラメータの型情報(TypeScriptクラス)
data デコレータに渡された引数(例:@Body('field')field

変換用カスタムPipeの例

文字列をトリムして小文字に変換するPipeを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// src/common/pipes/normalize-string.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class NormalizeStringPipe implements PipeTransform<string, string> {
  transform(value: string, metadata: ArgumentMetadata): string {
    if (typeof value !== 'string') {
      return value;
    }
    return value.trim().toLowerCase();
  }
}

使用例:

1
2
3
4
5
6
7
8
@Controller('users')
export class UsersController {
  @Get('search')
  search(@Query('username', NormalizeStringPipe) username: string) {
    // usernameは小文字かつトリム済み
    return this.usersService.searchByUsername(username);
  }
}

検証用カスタムPipeの例

特定のドメインルールに基づいた検証を行うPipeを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// src/common/pipes/parse-positive-int.pipe.ts
import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  BadRequestException,
} from '@nestjs/common';

@Injectable()
export class ParsePositiveIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const parsedValue = parseInt(value, 10);

    if (isNaN(parsedValue)) {
      throw new BadRequestException(
        `${metadata.data || 'パラメータ'}は数値である必要があります`,
      );
    }

    if (parsedValue <= 0) {
      throw new BadRequestException(
        `${metadata.data || 'パラメータ'}は正の整数である必要があります`,
      );
    }

    return parsedValue;
  }
}

パラメータ付きカスタムPipeの例

範囲を指定できるPipeを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/common/pipes/parse-int-range.pipe.ts
import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  BadRequestException,
} from '@nestjs/common';

interface ParseIntRangeOptions {
  min?: number;
  max?: number;
}

@Injectable()
export class ParseIntRangePipe implements PipeTransform<string, number> {
  constructor(private readonly options: ParseIntRangeOptions = {}) {}

  transform(value: string, metadata: ArgumentMetadata): number {
    const parsedValue = parseInt(value, 10);

    if (isNaN(parsedValue)) {
      throw new BadRequestException('数値を入力してください');
    }

    const { min, max } = this.options;

    if (min !== undefined && parsedValue < min) {
      throw new BadRequestException(`値は${min}以上である必要があります`);
    }

    if (max !== undefined && parsedValue > max) {
      throw new BadRequestException(`値は${max}以下である必要があります`);
    }

    return parsedValue;
  }
}

使用例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Controller('articles')
export class ArticlesController {
  @Get()
  findAll(
    @Query('page', new ParseIntRangePipe({ min: 1 })) page: number,
    @Query('limit', new ParseIntRangePipe({ min: 1, max: 100 })) limit: number,
  ) {
    return this.articlesService.findAll({ page, limit });
  }
}

データベースエンティティ取得Pipeの例

IDからエンティティを取得し、存在しなければ404エラーを返すPipeを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/common/pipes/entity-by-id.pipe.ts
import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  NotFoundException,
  Inject,
} from '@nestjs/common';
import { ArticlesService } from '../../articles/articles.service';

@Injectable()
export class ArticleByIdPipe implements PipeTransform<string, Promise<Article>> {
  constructor(
    @Inject(ArticlesService) private readonly articlesService: ArticlesService,
  ) {}

  async transform(value: string, metadata: ArgumentMetadata): Promise<Article> {
    const id = parseInt(value, 10);
    const article = await this.articlesService.findOne(id);

    if (!article) {
      throw new NotFoundException(`ID ${id} の記事が見つかりません`);
    }

    return article;
  }
}

使用例:

1
2
3
4
5
6
7
8
@Controller('articles')
export class ArticlesController {
  @Get(':id')
  findOne(@Param('id', ArticleByIdPipe) article: Article) {
    // articleはすでに取得済みのエンティティ
    return article;
  }
}

Pipeの適用スコープ

Pipeは複数のスコープで適用できます。

パラメータスコープ

特定のパラメータにのみ適用します。

1
2
3
4
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
  return this.articlesService.findOne(id);
}

メソッドスコープ

@UsePipes()デコレータを使用して、特定のルートハンドラに適用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { UsePipes } from '@nestjs/common';

@Controller('articles')
export class ArticlesController {
  @Post()
  @UsePipes(new ValidationPipe({ transform: true }))
  create(@Body() createArticleDto: CreateArticleDto) {
    return this.articlesService.create(createArticleDto);
  }
}

Controllerスコープ

Controller全体に適用します。

1
2
3
4
5
@Controller('articles')
@UsePipes(new ValidationPipe({ whitelist: true }))
export class ArticlesController {
  // すべてのハンドラにValidationPipeが適用される
}

グローバルスコープ

main.tsでアプリケーション全体に適用します。

1
2
3
4
5
6
// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}

依存性注入を使用してグローバルPipeを登録する場合は、APP_PIPEトークンを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// app.module.ts
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
  ],
})
export class AppModule {}

条件付きバリデーションの実装

特定の条件下でのみバリデーションを適用したい場合、@ValidateIf()デコレータを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { IsString, IsNotEmpty, IsOptional, ValidateIf, IsUrl } from 'class-validator';

export class CreateArticleDto {
  @IsString()
  @IsNotEmpty()
  title: string;

  @IsString()
  @IsNotEmpty()
  type: 'internal' | 'external';

  // type が 'internal' の場合のみ content を必須にする
  @ValidateIf((obj) => obj.type === 'internal')
  @IsString()
  @IsNotEmpty()
  content?: string;

  // type が 'external' の場合のみ externalUrl を必須にする
  @ValidateIf((obj) => obj.type === 'external')
  @IsUrl()
  @IsNotEmpty()
  externalUrl?: string;
}

バリデーショングループの活用

同じDTOクラスで複数のバリデーションパターンを定義する場合、グループ機能を使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { IsString, IsNotEmpty, IsOptional, MinLength } from 'class-validator';

export class UserDto {
  @IsString()
  @IsNotEmpty({ groups: ['create'] })
  @IsOptional({ groups: ['update'] })
  username: string;

  @IsString()
  @MinLength(8, { groups: ['create', 'update'] })
  @IsNotEmpty({ groups: ['create'] })
  @IsOptional({ groups: ['update'] })
  password: string;

  @IsString()
  @IsOptional({ always: true })  // すべてのグループで常にオプショナル
  bio?: string;
}

Controllerでグループを指定して検証します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Controller('users')
export class UsersController {
  @Post()
  @UsePipes(new ValidationPipe({ groups: ['create'] }))
  create(@Body() userDto: UserDto) {
    return this.usersService.create(userDto);
  }

  @Patch(':id')
  @UsePipes(new ValidationPipe({ groups: ['update'], skipMissingProperties: true }))
  update(@Param('id') id: string, @Body() userDto: UserDto) {
    return this.usersService.update(id, userDto);
  }
}

まとめ

本記事では、NestJSのPipeを活用した入力検証と変換について解説しました。

トピック ポイント
Pipeの役割 リクエストデータの変換と検証をControllerの前で実行
組み込みPipe ParseIntPipe、ValidationPipe等を用途に応じて使い分け
ValidationPipe class-validatorと組み合わせてDTOベースの堅牢な検証を実現
カスタムPipe ドメイン固有のビジネスロジックに基づいた変換・検証を実装可能
適用スコープ パラメータ、メソッド、Controller、グローバルの4レベルで設定可能

Pipeを適切に活用することで、不正なデータがControllerに到達することを防ぎ、セキュアで堅牢なAPIを構築できます。次のステップとして、Guardによる認可処理の実装を学ぶことで、より安全なAPIを構築できるようになります。

参考リンク