【2025最新型】icカードリーダー マイナンバーカード対応 USB-A&Type-C 接触型 sdカードリーダー 設置不要 自宅で確定申告 国税電子申告 納税システム e-Tax対応マイナンバーカード ICチップ付いた住民基本台帳カード SDカード/マイナンバーカード/キャッシュカード その他対応 Windows11/10/8/7/Vista/macOS 10.16以降 日本語説明書付き
¥1,299 (2025年4月25日 13:05 GMT +09:00 時点 - 詳細はこちら価格および発送可能時期は表示された日付/時刻の時点のものであり、変更される場合があります。本商品の購入においては、購入の時点で当該の Amazon サイトに表示されている価格および発送可能時期の情報が適用されます。)【2025新版・MFi認証・自動接続】 iPhone hdmi変換ケーブル ライトニング 設定不要・ APP不要・ 給電不要 ・1080PプルHD TV大画面 音声同期出力 ライトニング hdmi iphone tv 変換ケーブル テレビに映す 遅延なし簡単接続 iPhone/iPad などに対応 日本語取説付き(iOS13 - iOS18対応)
¥1,699 (2025年4月25日 13:08 GMT +09:00 時点 - 詳細はこちら価格および発送可能時期は表示された日付/時刻の時点のものであり、変更される場合があります。本商品の購入においては、購入の時点で当該の Amazon サイトに表示されている価格および発送可能時期の情報が適用されます。)
はじめに
今回の記事では、 「NestJSのDTOの実装方法」 をまとめました!前半では知識の整理や理解(インプット)、後半では要件やテーブルモデルから実際にDTOを実装する(アウトプット)のような形式になっています!
私も実務の中でDTOの実装は行っているため、既存の実装を参考にしながら実装することは可能 です。しかし、「世界一流エンジニアの思考法」という書籍で述べられていましたが、実装スピードを高めるには、「調べれば実装できること」 を減らし、「調べなくても実装できること」 を増やす必要があると考えています。
記事の最後に練習問題を用意しているので、私と一緒に 「既存の実装を確認せず、爆速で実装できるレベル」 を目指しましょう!
前提
この記事は、TypeScriptやNestJSの実装経験 があり、DTOについてもっと理解したい!という人向けに記述しています。
経験のある人や普段の実務でNestJSのDTOを実装している人は、「問題を解く」→「該当箇所のインプットを確認する」といったほうが効率が良さそうです。
各内容へのリンク
インプット編
アウトプット編
インプット編
1. ドキュメント化
@ApiProperty({
description: '発送日',
example: '2025-01-01',
type: String, ⇦ ここでの型定義は大文字始まり「String」「Number」などにすること
})
【補足(1)】typeに「string」や「number」を指定してはダメなの?
⇨ @ApiProperty()
の type は JavaScript のコンストラクタ関数(型のクラス) を指定する仕様 になっており、「string」や「number」は型エラーが発生する可能性があるそうです。
2. 型定義 (下記で定義した以外の値を受け取るとエラーになる)
@IsString()
⇨ 文字列であることを保証
@IsBoolean()
⇨ 真偽値であることを保証
@IsEnum(ENUM_VALUE)
⇨ 指定したEnumのいずれかの値であることを保証
@IsDate()
⇨ Data型であることを保証
@IsNumber()
⇨ 数値であることを保証
@IsObject()
⇨ オブジェクト型であることを保証
@IsArray()
⇨ 配列であることを保証
3. バリデーション
@IsNotEmpty()
⇨ 「null」「undefined」「空の値」を許可しない
@IsOptional()
⇨ リクエストがなくても、エラーにならない (注意:リクエストを受け取った場合は、型定義にバリデーションに従う)
@ValidateNested({ each: true })
⇨ リクエストでオブジェクトの配列を受け取った際、全てのデータにvalidationを適用する
@ValidateNested()
⇨ リクエストでオブジェクトを受け取った際、validationを適用する
@ValidateIf((_, value) => value !== null)
⇨ 「Null」を許容
【補足】@IsOptional
との違いは?
1 @IsOptional
は「undifined」の場合に処理をスキップ
2 @ValidateIf((_, value) => value !== null)
は「null」の場合に処理をスキップ
4. 型変換、値のカスタム
4-1 @Type(() => XXXX)
@Type(() => Number)
⇨ 受け取った値を数値に変換
@Type(() => Date)
⇨ 受け取った値をDate型に変換
【補足】
1 どんな時に型変換が必要なの?
- パターン1 (GETメソッドの検索パラメータでDTOを指定)
⇨ リクエストパラメータ(クエリ)で受け取った場合、値は 基本的に「文字列」で受け取る ため、数値に変換する必要がある
- パターン2 (日付を文字列で受け取る場合を考慮)
⇨ ISO 8601 形式(2025-04-01T12:30:00Z)
の文字列を受け取る場合、「@Type(() => Date)
」を使って「Date型」に変換する必要がある
4-2 @Transform(({ value }) => xxxx)
@Transform(({ value }) => startOfDay(value), { toClassOnly: true })
⇨ 受け取った値を「日付の開始時刻(00:00:00)」に変換する。 (toClassOnly: true
)はリクエストからクラスに変換する際にのみ適用され、レスポンス時には適用されない!
@Transform(({ value }) => typeof value === ‘string' && value.toLowerCase() === 'true')
⇨ 文字列で受け取った真偽値(ex: “true“)を真偽値(true)に変換する
@Transform(({ value }) => (value == null ? null : endOfDay(value)))
⇨ nullを許容するパターン。下記の実装でnullの場合にバリデーションをスキップする
アウトプット編
下記に「実装についての要件」と「どんなリクエストが必要か」についての記述があります。これらを確認して、実際にDTOを実装しましょう。
例題(1) 【SearchUserDto】
要件
- ユーザーの検索DTOを実装したい
-
@ApiProperty
の実装は任意です
必要なリクエスト内容
解答例
export class SearchUserDto {
@ApiPropertyOptional({
description: 'ユーザー名',
type: String,
})
@IsString()
@IsOptional()
name?: string;
@ApiPropertyOptional({
description: 'ユーザーコード',
type: String,
})
@IsString()
@IsOptional()
code?: string;
@ApiPropertyOptional({
description: '年齢',
type: Number,
})
@IsNumber()
@Type(() => Number)
@IsOptional()
age?: number;
}
実装のポイント
- 「
@ApiPropertyOptional
」のtypeは「JavaScript のコンストラクタ関数」で指定しよう! - 検索用のDTOなので、「
@IsOptional()
」をつけよう! - 「name?: string;」のようにオプショナルにしよう!
例題(2) 【CreateUserDto】
要件
- ユーザーの新規作成DTOを実装したい
-
@ApiProperty
の実装は任意です
必要なリクエスト内容
-
ユーザーコード(必須) (code: string)
-
ユーザー名(必須) (name: string)
-
ニックネーム(空文字を許容) (nickName: string)
-
プロフィール (profile: string)
解答例
export class CreateUserDto {
@ApiProperty({
description: 'ユーザーコード',
type: String,
})
@IsString()
@IsNotEmpty()
code: string;
@ApiProperty({
description: 'ユーザー名',
type: String,
})
@IsString()
@IsNotEmpty()
name: string;
@ApiProperty({
description: 'ニックネーム',
type: String,
})
@IsString()
nickName: string;
@ApiProperty({
description: 'プロフィール',
type: String,
})
@IsString()
profile: string;
}
実装のポイント
- 必須項目には、
@IsNotEmpty()
をつけよう! - 住所(location)と備考(note)は未入力時に空文字を送るので、「
@IsNotEmpty()
」はつけません!
練習問題(1) 【SearchPostDto】
要件
- 投稿の検索DTOを実装したい
-
@ApiProperty
の実装は必須とします
必要なリクエスト内容
- 投稿名 (name: string)
- 投稿番号 (code: string)
- ユーザーID (userId: number)
- コメントID (commentId: number)
- お気に入りID (favoriteId: number)
解答例
export class SearchPostDto {
@ApiProperty({
description: '投稿名',
type: String,
})
@IsString()
@Optional()
name?: string;
@ApiProperty({
description: '投稿番号',
type: String,
})
@IsString()
@IsOptional()
code?: string;
@ApiProperty({
description: 'ユーザーID',
type: Number,
})
@IsNumber()
@IsOptional()
userId?: number;
@ApiProperty({
description: 'コメントID',
type: Numer,
})
@IsNumber()
@IsOptional()
commentId?: number;
@ApiProperty({
description: 'お気に入りID',
type: Number,
})
@IsNumber()
@IsOptional()
favoriteId?: number
}
練習問題(2) 【CreateProfileDto】
要件
- プロフィールの新規作成DTOを実装したい
-
@ApiProperty
の実装は必須とします
必要なリクエスト内容
- 身長 (height: string)
- 体型 (bodyType: BodyType)
- 職業 (occupation: string | undefined)
- 学歴 (education: EducationType | null)
- 出身地 (hometown: string)
- 居住地 (location: string | null)
- 趣味 (hobbyIds: number[])
- 誕生日 (birthDay: Date)
- 休日の過ごし方 (weekendActivities: WeekendActivitiesDto)
実装の上で注意すること
- 「BodyType」と「EducationType」はEnumで実装されています
- 誕生日については、「リクエスト受け取り時は文字列で受け取ること」「日付の開始時刻に変換すること」
- 「WeekendActivitiesDto」はオブジェクトです
解答例
export class SearchPostDto {
@ApiProperty({
description: '身長',
type: String,
})
@IsString()
@IsNotEmpty() ⇦ これがないと「空文字」を許容してしまいます!
height: string;
@ApiProperty({
description: '体型',
type: BodyType,
})
@IsEnum(BodyType)
bodyType: BodyType;
@ApiProperty({
description: '職業',
type: Number,
})
@IsNumber()
@IsOptional()
occupation?: string;
@ApiProperty({
description: '学歴',
type: EducationType,
})
@IsEnum(EducationType)
@ValidateIf((_, value) => value !== null)
education: EducationType | null;
@ApiProperty({
description: '出身地',
type: String,
})
@IsString()
@IsNotEmpty()
hometown: string
@ApiProperty({
description: '居住地',
type: String,
})
@IsString()
@ValidateIf((_, value) => value !== null)
location: string | null
@ApiProperty({
description: '趣味',
type: Number,
isArray: true,
})
@IsArray({ each: true })
@IsNumber({}, { each: true }) ⇦ 配列の中身が全てNumber型であることを保証します
hobbyIds: number[]
@ApiProperty({
description: '誕生日',
type: String, ⇦ リクエスト時の型を指定するため、「String」です!
})
@Transform(({ value }) => startOfDay(value))
@IsDate()
@Type(() => Date)
birthDay: Date
@ApiProperty({
description: '休日の過ごし方',
type: WeekendActivitiesDto,
})
@ValidateNested()
@IsObject()
@Type(() => WeekendActivitiesDto) ⇦ TypeScriptの型情報は実行時に消えてしまうため必須!!
weekendActivities: WeekendActivitiesDto
}
最後に
記事の中でも「解答例」という書き方をしましたが、あくまで実装方法は一例です!もっとシンプルな書き方や詳細な書き方があるかもしれません!
また、アウトプットで問題が簡単に解けた人は「既存のDTO」⇨「問題の作成」をやってみると、より理解が進むと思います!よかったらやってみてください!
株式会社シンシア
株式会社xincereでは、実務未経験のエンジニアの方や学生エンジニアインターンを採用し一緒に働いています。
※ シンシアにおける働き方の様子はこちら
シンシアでは、年間100人程度の実務未経験の方が応募し技術面接を受けます。
その経験を通し、実務未経験者の方にぜひ身につけて欲しい技術力(文法)をここでは紹介していきます。