← 解説「DTO — リクエストの形を定義する」に戻る
サンプルコードで身につける: DTO — リクエストの形を定義する
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1最小のDTO
インラインの型注釈を専用クラスに昇格させた最小の DTO です。「POST /tasks のボディの形」に CreateTaskDto という名前が付き、コントローラとサービスの両方から同じ形を参照できるようになります。
import { Body, Controller, Post } from "@nestjs/common";
// 「POST /tasks のボディの形」に名前を付ける
export class CreateTaskDto {
title: string;
deadline: string;
}
@Controller("tasks")
export class TasksController {
@Post()
create(@Body() dto: CreateTaskDto) {
return { created: dto.title };
}
}
2操作ごとにDTOを分ける
作成用と更新用で DTO を分ける定番パターンです。作成は全項目必須、更新は変えたい項目だけ送ればよい、という API 仕様の違いを型のレベルでそのまま表現できます。
// 作成時: 全項目が必須
export class CreateUserDto {
name: string;
email: string;
age: number;
}
// 更新時: 変えたい項目だけ送ればよいので全項目オプション
export class UpdateUserDto {
name?: string;
email?: string;
age?: number;
}
// 「作成と更新で必須項目が違う」という仕様を型で表現できる
// 操作名 + Dto という命名で、用途がファイル名から分かる
3class は実行時に残る
DTO を interface ではなく class で書く理由を実行して確かめる例です。class はコンパイル後も値として残るため実行時に参照できますが、interface は消えてしまいます。この性質が次レッスンの検証の土台になります。
TypeScript
4ネストしたDTO
DTO の中に別の DTO を入れ子にする例です。住所のような階層を持つ JSON も、オブジェクトのプロパティと配列でそのまま型にできます。深い構造は小さな DTO の組み合わせで表すのが読みやすさの定石です。
export class AddressDto {
postalCode: string;
city: string;
}
export class CreateCompanyDto {
companyName: string;
address: AddressDto; // DTO の中に DTO を入れ子にできる
branches: AddressDto[]; // 配列もそのまま表現できる
}
// 対応する JSON:
// {
// "companyName": "Example社",
// "address": { "postalCode": "100-0001", "city": "千代田区" },
// "branches": [{ "postalCode": "530-0001", "city": "大阪市" }]
// }
5ページネーション用のクエリDTO
一覧系 API で使い回すページネーション DTO の実務例です。ボディだけでなく @Query() の型注釈にも DTO を使えること、クエリ由来の値は常に文字列で届くことを合わせて示します。
import { Controller, Get, Query } from "@nestjs/common";
// 一覧系 API すべてで使い回す定番 DTO(クエリ文字列用)
export class PaginationQueryDto {
page?: string; // クエリ由来の値は常に文字列で届く
limit?: string; // (数値への変換は後のレッスンで)
}
@Controller("articles")
export class ArticlesController {
@Get() // GET /articles?page=2&limit=10
findAll(@Query() query: PaginationQueryDto) {
return {
page: query.page ?? "1",
limit: query.limit ?? "20",
};
}
}