awesome-hacks
Docs

NestJS DTO / ValidationPipe 入門

DTOとValidationPipeの役割を理解し、NestJSで入力検証付きのPOST APIを最小構成で実装する

最終更新:2026/05/07

前回の記事ではNestJSの基礎を学んだ。

なぜ最初にDTO / ValidationPipeか

APIで受け取るデータがDTOに定義された形になっているかValidationPipeを用いて検証を行うことで、不正な挙動を回避しつつ、Controller以降では入力値が最低限まともである前提で書くことができるようにする。 実際、NestJS実務ではDTO / ValidationPipeはかなり高頻度で登場する。

  • CRUD/API/認証/Swaggerの土台になる
  • APIの入力境界を最初に固められる
  • 後からバグ修正するより、入口で弾く方が安い

ここを飛ばすと、次のようなコードを見た時に詰まりやすい。

create(@Body() dto: CreateUserDto)
  • なぜDTOを挟むのか
  • なぜinterfaceではなくclassなのか
  • どこでバリデーションされているのか

そもそもDTOとValidationPipeとは

DTO(Data Transfer Object)

NestJSでのDTOは、APIで受け取るデータの形を定義するためのクラス。

  • POST /users なら「idとnameを受け取る」のように入力項目を明確にする
  • class-validator のDecoratorを使って、入力ルール(必須/文字数/メール形式)をDTOに書ける
  • EntityやDBモデルとは役割が違い、DTOはあくまで「API入口の契約」を表す

要するに、

DTO = API入口で受け取るデータ仕様

ValidationPipe

ValidationPipeは、@Body() で受け取る値をDTOルールで検証する仕組み。

  • DTOに書いた @IsEmail()@Length() を実行する
  • 不正な入力ならController処理に入る前に400エラーで止める
  • whitelist などのオプションで、余計な値を落とす/弾くを制御できる

要するに、

ValidationPipe = DTOルールを実際に適用して入力検証する実行装置

今回やること

nestjs-basics で作成した api-sample に対して、DTO / ValidationPipeを追加する。(既存プロジェクトに機能追加)

サンプル実装手順

  1. 前回記事で作成した既存プロジェクトへ移動
cd nestjs-practice/api-sample
  1. 必要ライブラリ追加(DTO/Validation用)
npm install class-validator class-transformer
  • class-validator: @Length() などの検証ルール本体
  • class-transformer: 受け取ったJSONをDTOクラスへ変換するために利用
  1. DTO/Validationを差し込む先(controller/service/module)を用意:users 機能があるか確認

前回用意した users 機能に「DTO + Validation」を追加する。

users がまだ無い場合は次を実行する。

nest g module users
nest g controller users
nest g service users
  • module: users機能の配線
  • controller: HTTP入口(ルーティング)
  • service: 処理の中身(ロジック)
  1. DTO作成

受け入れる入力の契約を明文化する。ここで idname のルールを固定することで、以降の処理が前提を持って書ける。
src/users/dto/create-user.dto.ts を作成する。

import { IsString, Length } from "class-validator";

export class CreateUserDto {
  @IsString()
  @Length(1, 30)
  id: string;

  @IsString()
  @Length(1, 50)
  name: string;
}

ここで重要なのは次の理解。

DTO = API入力値の型 + バリデーション定義
  1. ValidationPipe設定

src/main.tsValidationPipe を追加する。

import { ValidationPipe } from "@nestjs/common"; // 追加
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // ここから追加
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
      forbidNonWhitelisted: true,
    }),
  );
  // ここまで追加

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

この手順の意図は「DTOに書いたルールを全APIで実際に有効化する」こと。
DTOだけ作っても、Pipeを設定しない限り自動検証は走らない。

  1. Controller修正

src/users/users.controller.ts を次のようにする。

import { Body, Controller, Post } from "@nestjs/common";
import { CreateUserDto } from "./dto/create-user.dto";

@Controller("users")
export class UsersController {
  @Post()
  create(@Body() dto: CreateUserDto) {
    return {
      message: "user created",
      data: dto,
    };
  }
}

この手順の意図は「@Body() 入力をDTO経由で受ける」こと。
create(@Body() dto: CreateUserDto) にすることで、ValidationPipeがDTOルールを適用できる。

  1. 動作確認

起動:

npm run start:dev

この手順の意図は「正常系と異常系の両方で、検証が効いていることを確認する」こと。
200だけでなく400を確認して初めて、設定が正しく効いていると言える。

正常系:

curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"id":"u_3","name":"Taro"}'

バリデーションエラー確認:

curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"id":"","name":""}'

2つ目は 400 エラーになるはず。

ここで理解すべきこと(超重要)

DTOの役割

DTOはAPIで受け取るデータ定義であり、次の意味を持つ。

API入口専用の型

EntityやDBモデルと同一にしないのが基本。

ValidationPipeの役割

ValidationPipeは、DTOに書いたルールで入力値を検証する仕組み。

Controllerに入る前に、入力をチェックする

whitelist の意味

whitelist: true

DTOに存在しない値を削除する。

forbidNonWhitelisted の意味

forbidNonWhitelisted: true

DTOに存在しない値が来た時点で400エラーにする。
実務では「想定外入力を静かに通さない」ために重要。

transform の意味

transform: true

@Body() の値をDTOクラスへ変換する。
数値変換やPipe連携(ParseIntPipe など)と組み合わせる時に有効。

よくある落とし穴

  • DTOを interface で定義してしまう(Decoratorが効かないのでclassを使う)
  • ValidationPipe を設定しておらず、Decoratorを書いても検証されない
  • DTOを作っただけでController側の @Body() 型をDTOにしていない
  • whitelist だけ有効で forbidNonWhitelisted が無効だと、余計な値が黙って落ちる

参考

NestJS公式サイト