NestJSでは、@Body()や@Param()などの組み込みパラメータデコレータを使用してリクエストデータにアクセスします。しかし、実際の開発では認証済みユーザー情報の取得やリクエストからの特定データ抽出など、同じパターンを複数のコントローラで繰り返すことが多くなります。本記事では、createParamDecorator()を使用したカスタムパラメータデコレータの作成から、applyDecorators()による複数デコレータの合成パターンまでを実践的に解説します。
実行環境と前提条件#
本記事の内容を実践するにあたり、以下の環境を前提としています。
| 項目 |
バージョン・要件 |
| Node.js |
20以上 |
| npm |
10以上 |
| NestJS |
11.x |
| TypeScript |
5.x |
| OS |
Windows / macOS / Linux |
| エディタ |
VS Code(推奨) |
事前に以下の準備を完了してください。
- NestJS CLIのインストール済み
- NestJSプロジェクトの作成済み
- TypeScriptデコレータの基本概念の理解
- 認証処理の実装済み(
request.userにユーザー情報が格納されている状態が望ましい)
NestJSプロジェクトの作成方法はNestJS入門記事、JWT認証の実装はJWT認証記事を参照してください。
デコレータの種類と役割#
NestJSで使用するデコレータは、その適用対象によって以下のように分類されます。
flowchart TB
subgraph Decorators["NestJSデコレータの種類"]
direction TB
CD[クラスデコレータ]
MD[メソッドデコレータ]
PD[パラメータデコレータ]
PRD[プロパティデコレータ]
end
CD --> CD_EX["@Controller()
@Module()
@Injectable()"]
MD --> MD_EX["@Get()
@Post()
@UseGuards()"]
PD --> PD_EX["@Body()
@Param()
@Query()"]
PRD --> PRD_EX["@Inject()"]
style CD fill:#E91E63,color:#fff
style MD fill:#9C27B0,color:#fff
style PD fill:#3F51B5,color:#fff
style PRD fill:#009688,color:#fffパラメータデコレータとメソッドデコレータの違い#
| 項目 |
パラメータデコレータ |
メソッドデコレータ |
| 適用対象 |
メソッドの引数 |
メソッド自体 |
| 目的 |
リクエストからデータを抽出して引数に注入 |
メソッドの動作を拡張・修飾 |
| 作成方法 |
createParamDecorator() |
標準のTypeScriptデコレータ or SetMetadata() |
| 例 |
@Body(), @Query(), @User() |
@Get(), @UseGuards(), @Roles() |
| 実行タイミング |
ルートハンドラ実行時 |
ルートハンドラ定義時(メタデータ設定) |
組み込みパラメータデコレータの確認#
カスタムデコレータを作成する前に、NestJSが提供する組み込みパラメータデコレータを確認しましょう。これらはすべてリクエストオブジェクトから特定のデータを抽出します。
| デコレータ |
抽出対象 |
Expressオブジェクト |
@Request(), @Req() |
リクエストオブジェクト全体 |
req |
@Response(), @Res() |
レスポンスオブジェクト |
res |
@Param(key?: string) |
ルートパラメータ |
req.params / req.params[key] |
@Body(key?: string) |
リクエストボディ |
req.body / req.body[key] |
@Query(key?: string) |
クエリパラメータ |
req.query / req.query[key] |
@Headers(name?: string) |
リクエストヘッダー |
req.headers / req.headers[name] |
@Ip() |
クライアントIPアドレス |
req.ip |
@Session() |
セッションオブジェクト |
req.session |
@HostParam() |
ホストパラメータ |
req.hosts |
組み込みデコレータの使用例#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import { Controller, Get, Param, Query, Headers } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get(':id')
findOne(
@Param('id') id: string,
@Query('include') include: string,
@Headers('authorization') auth: string,
) {
return {
userId: id,
include,
hasAuth: !!auth,
};
}
}
|
カスタムパラメータデコレータが必要な理由#
認証処理でリクエストオブジェクトにユーザー情報を付与した場合、毎回@Req()デコレータを使用して手動で抽出する必要があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('profile')
export class ProfileController {
@Get()
getProfile(@Req() request: Request) {
// 毎回このようにuserを取り出す必要がある
const user = request.user;
return user;
}
@Get('settings')
getSettings(@Req() request: Request) {
// 同じパターンを繰り返す
const user = request.user;
return { userId: user.id, settings: {} };
}
}
|
この方法には以下の問題点があります。
- 同じ抽出ロジックを繰り返し記述する必要がある
request.userの型安全性が確保されない
- コードの可読性が低下する
- テスト時にリクエストオブジェクト全体をモックする必要がある
カスタムデコレータを使用すると、これらの問題を解決できます。
createParamDecorator()の基本#
createParamDecorator()は、NestJSが提供するカスタムパラメータデコレータ作成用のファクトリ関数です。
関数シグネチャ#
1
2
3
4
5
6
7
8
9
|
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CustomDecorator = createParamDecorator(
(data: T, ctx: ExecutionContext) => {
// dataはデコレータに渡された引数
// ctxはExecutionContextインスタンス
// 返り値がメソッドの引数として注入される
},
);
|
ExecutionContextの構造#
ExecutionContextはリクエストに関する情報を提供するインターフェースです。
classDiagram
class ExecutionContext {
+switchToHttp() HttpArgumentsHost
+switchToRpc() RpcArgumentsHost
+switchToWs() WsArgumentsHost
+getClass() Type
+getHandler() Function
+getType() string
}
class HttpArgumentsHost {
+getRequest() Request
+getResponse() Response
+getNext() NextFunction
}
ExecutionContext --> HttpArgumentsHost : switchToHttp()@User()デコレータの実装#
認証済みユーザー情報を取得する@User()デコレータを実装しましょう。
基本的な@User()デコレータ#
まず、src/common/decoratorsディレクトリを作成し、デコレータを配置します。
1
2
3
4
5
6
7
8
9
|
// src/common/decorators/user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
|
コントローラでの使用例は以下のようになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '../auth/auth.guard';
import { User } from '../common/decorators/user.decorator';
interface UserPayload {
id: number;
email: string;
roles: string[];
}
@Controller('profile')
@UseGuards(AuthGuard)
export class ProfileController {
@Get()
getProfile(@User() user: UserPayload) {
return user;
}
}
|
特定のプロパティを抽出する@User()デコレータ#
デコレータに引数を渡すことで、ユーザーオブジェクトから特定のプロパティのみを抽出できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// src/common/decorators/user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export interface UserPayload {
id: number;
email: string;
firstName: string;
lastName: string;
roles: string[];
}
export const User = createParamDecorator(
(data: keyof UserPayload | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user: UserPayload = request.user;
// dataが指定されていれば特定のプロパティを返す
return data ? user?.[data] : user;
},
);
|
コントローラでは以下のように使用します。
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
|
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '../auth/auth.guard';
import { User, UserPayload } from '../common/decorators/user.decorator';
@Controller('profile')
@UseGuards(AuthGuard)
export class ProfileController {
// ユーザー全体を取得
@Get()
getProfile(@User() user: UserPayload) {
return user;
}
// メールアドレスのみを取得
@Get('email')
getEmail(@User('email') email: string) {
return { email };
}
// ユーザーIDのみを取得
@Get('id')
getUserId(@User('id') userId: number) {
return { userId };
}
// ロール情報のみを取得
@Get('roles')
getRoles(@User('roles') roles: string[]) {
return { roles };
}
}
|
型安全性を強化した@User()デコレータ#
TypeScriptのジェネリクスを活用して、型安全性をさらに強化できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// src/common/decorators/user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export interface UserPayload {
id: number;
email: string;
firstName: string;
lastName: string;
roles: string[];
}
export const User = createParamDecorator<keyof UserPayload | undefined>(
(data, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user: UserPayload = request.user;
if (!user) {
return null;
}
return data ? user[data] : user;
},
);
|
実践的なカスタムデコレータの例#
@ClientIp()デコレータ#
プロキシ経由のリクエストにも対応したクライアントIPアドレス取得デコレータです。
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
|
// src/common/decorators/client-ip.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const ClientIp = createParamDecorator(
(data: unknown, ctx: ExecutionContext): string => {
const request = ctx.switchToHttp().getRequest();
// X-Forwarded-Forヘッダーからプロキシ経由のIPを取得
const forwardedFor = request.headers['x-forwarded-for'];
if (forwardedFor) {
// カンマ区切りの最初のIPアドレスを使用
const ips = forwardedFor.split(',');
return ips[0].trim();
}
// X-Real-IPヘッダーを確認
const realIp = request.headers['x-real-ip'];
if (realIp) {
return realIp;
}
// 直接接続のIPアドレスを返す
return request.ip || request.connection?.remoteAddress || 'unknown';
},
);
|
@UserAgent()デコレータ#
リクエストのUser-Agent情報を解析して取得するデコレータです。
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
|
// src/common/decorators/user-agent.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export interface UserAgentInfo {
raw: string;
browser: string;
os: string;
isMobile: boolean;
}
export const UserAgent = createParamDecorator(
(data: keyof UserAgentInfo | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const userAgent: string = request.headers['user-agent'] || '';
const info: UserAgentInfo = {
raw: userAgent,
browser: detectBrowser(userAgent),
os: detectOS(userAgent),
isMobile: /Mobile|Android|iPhone|iPad/i.test(userAgent),
};
return data ? info[data] : info;
},
);
function detectBrowser(ua: string): string {
if (ua.includes('Chrome') && !ua.includes('Edge')) return 'Chrome';
if (ua.includes('Firefox')) return 'Firefox';
if (ua.includes('Safari') && !ua.includes('Chrome')) return 'Safari';
if (ua.includes('Edge')) return 'Edge';
return 'Unknown';
}
function detectOS(ua: string): string {
if (ua.includes('Windows')) return 'Windows';
if (ua.includes('Mac OS')) return 'macOS';
if (ua.includes('Linux')) return 'Linux';
if (ua.includes('Android')) return 'Android';
if (ua.includes('iOS') || ua.includes('iPhone')) return 'iOS';
return 'Unknown';
}
|
ページネーションパラメータを一括取得するデコレータです。
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
|
// src/common/decorators/pagination.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export interface PaginationParams {
page: number;
limit: number;
offset: number;
sortBy: string;
sortOrder: 'asc' | 'desc';
}
export const Pagination = createParamDecorator(
(
defaults: Partial<PaginationParams> | undefined,
ctx: ExecutionContext,
): PaginationParams => {
const request = ctx.switchToHttp().getRequest();
const query = request.query;
const defaultValues = {
page: 1,
limit: 10,
sortBy: 'createdAt',
sortOrder: 'desc' as const,
...defaults,
};
const page = Math.max(1, parseInt(query.page, 10) || defaultValues.page);
const limit = Math.min(
100,
Math.max(1, parseInt(query.limit, 10) || defaultValues.limit),
);
const sortBy = query.sortBy || defaultValues.sortBy;
const sortOrder =
query.sortOrder === 'asc' || query.sortOrder === 'desc'
? query.sortOrder
: defaultValues.sortOrder;
return {
page,
limit,
offset: (page - 1) * limit,
sortBy,
sortOrder,
};
},
);
|
コントローラでの使用例は以下のようになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import { Controller, Get } from '@nestjs/common';
import { Pagination, PaginationParams } from '../common/decorators/pagination.decorator';
@Controller('articles')
export class ArticlesController {
// デフォルト値を使用
@Get()
findAll(@Pagination() pagination: PaginationParams) {
console.log(pagination);
// { page: 1, limit: 10, offset: 0, sortBy: 'createdAt', sortOrder: 'desc' }
return this.articlesService.findAll(pagination);
}
// カスタムデフォルト値を指定
@Get('popular')
findPopular(
@Pagination({ limit: 5, sortBy: 'viewCount' }) pagination: PaginationParams,
) {
return this.articlesService.findPopular(pagination);
}
}
|
カスタムデコレータでPipeを使用する#
カスタムパラメータデコレータは、組み込みデコレータと同様にPipeを適用できます。
ValidationPipeとの併用#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import { Controller, Get, UseGuards } from '@nestjs/common';
import { ValidationPipe } from '@nestjs/common';
import { User } from '../common/decorators/user.decorator';
import { UserPayload } from '../common/interfaces/user-payload.interface';
@Controller('profile')
export class ProfileController {
@Get()
getProfile(
@User(new ValidationPipe({ validateCustomDecorators: true }))
user: UserPayload,
) {
return user;
}
}
|
validateCustomDecorators: trueオプションを設定することで、カスタムデコレータで取得した値にもバリデーションが適用されます。
カスタムPipeとの併用#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// src/common/pipes/parse-user.pipe.ts
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
import { UserPayload } from '../interfaces/user-payload.interface';
@Injectable()
export class ParseUserPipe implements PipeTransform<UserPayload> {
transform(value: UserPayload): UserPayload {
if (!value) {
throw new BadRequestException('User not found in request');
}
if (!value.id || !value.email) {
throw new BadRequestException('Invalid user payload');
}
return value;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
import { Controller, Get, UseGuards } from '@nestjs/common';
import { User } from '../common/decorators/user.decorator';
import { ParseUserPipe } from '../common/pipes/parse-user.pipe';
import { UserPayload } from '../common/interfaces/user-payload.interface';
@Controller('profile')
export class ProfileController {
@Get()
getProfile(@User(ParseUserPipe) user: UserPayload) {
return user;
}
}
|
メソッドデコレータの作成#
メソッドデコレータは、SetMetadata()を使用してメタデータを設定するパターンがよく使われます。
1
2
3
4
5
|
// src/common/decorators/public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
1
2
3
4
5
|
// src/common/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
|
Guardでのメタデータ取得#
Reflectorを使用してデコレータで設定したメタデータを取得します。
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
|
// src/auth/auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from '../common/decorators/public.decorator';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// @Public()デコレータが付いているかチェック
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
// 認証ロジック
const request = context.switchToHttp().getRequest();
return !!request.user;
}
}
|
applyDecorators()によるデコレータ合成#
複数のデコレータを1つにまとめることで、コードの可読性と再利用性を向上させることができます。
デコレータ合成の基本#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// src/common/decorators/auth.decorator.ts
import { applyDecorators, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { AuthGuard } from '../../auth/auth.guard';
import { RolesGuard } from '../../auth/roles.guard';
import { Roles } from './roles.decorator';
export function Auth(...roles: string[]) {
return applyDecorators(
Roles(...roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized' }),
);
}
|
コントローラでは、複数のデコレータを1つの@Auth()で置き換えられます。
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 { Controller, Get, Post, Body } from '@nestjs/common';
import { Auth } from '../common/decorators/auth.decorator';
@Controller('admin')
export class AdminController {
// Before: 複数のデコレータを個別に記述
// @UseGuards(AuthGuard, RolesGuard)
// @Roles('admin')
// @ApiBearerAuth()
// @ApiUnauthorizedResponse({ description: 'Unauthorized' })
// After: 1つの合成デコレータで完結
@Get('users')
@Auth('admin')
getAllUsers() {
return this.userService.findAll();
}
@Get('stats')
@Auth('admin', 'moderator')
getStats() {
return this.statsService.getStats();
}
}
|
APIエンドポイント用デコレータの合成#
Swagger/OpenAPIデコレータを含めた包括的なエンドポイントデコレータを作成できます。
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
|
// src/common/decorators/api-paginated-response.decorator.ts
import { applyDecorators, Type } from '@nestjs/common';
import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger';
import { PaginatedResponseDto } from '../dto/paginated-response.dto';
export function ApiPaginatedResponse<T extends Type>(model: T) {
return applyDecorators(
ApiExtraModels(PaginatedResponseDto, model),
ApiOkResponse({
description: 'Paginated response',
schema: {
allOf: [
{ $ref: getSchemaPath(PaginatedResponseDto) },
{
properties: {
data: {
type: 'array',
items: { $ref: getSchemaPath(model) },
},
},
},
],
},
}),
);
}
|
CRUDエンドポイント用デコレータセット#
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
|
// src/common/decorators/crud.decorator.ts
import { applyDecorators, Get, Post, Put, Delete, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiParam, ApiBody } from '@nestjs/swagger';
export function ApiCreate(resourceName: string, dtoType: any) {
return applyDecorators(
Post(),
HttpCode(HttpStatus.CREATED),
ApiOperation({ summary: `Create a new ${resourceName}` }),
ApiBody({ type: dtoType }),
ApiResponse({ status: 201, description: `${resourceName} created successfully` }),
ApiResponse({ status: 400, description: 'Invalid input' }),
);
}
export function ApiGetOne(resourceName: string) {
return applyDecorators(
Get(':id'),
ApiOperation({ summary: `Get a ${resourceName} by ID` }),
ApiParam({ name: 'id', description: `${resourceName} ID` }),
ApiResponse({ status: 200, description: `${resourceName} found` }),
ApiResponse({ status: 404, description: `${resourceName} not found` }),
);
}
export function ApiUpdate(resourceName: string, dtoType: any) {
return applyDecorators(
Put(':id'),
ApiOperation({ summary: `Update a ${resourceName}` }),
ApiParam({ name: 'id', description: `${resourceName} ID` }),
ApiBody({ type: dtoType }),
ApiResponse({ status: 200, description: `${resourceName} updated successfully` }),
ApiResponse({ status: 404, description: `${resourceName} not found` }),
);
}
export function ApiDelete(resourceName: string) {
return applyDecorators(
Delete(':id'),
HttpCode(HttpStatus.NO_CONTENT),
ApiOperation({ summary: `Delete a ${resourceName}` }),
ApiParam({ name: 'id', description: `${resourceName} ID` }),
ApiResponse({ status: 204, description: `${resourceName} deleted successfully` }),
ApiResponse({ status: 404, description: `${resourceName} not found` }),
);
}
|
WebSocket用カスタムデコレータ#
WebSocketゲートウェイでもカスタムデコレータを活用できます。
1
2
3
4
5
6
7
8
9
10
|
// src/common/decorators/ws-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Socket } from 'socket.io';
export const WsUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const client: Socket = ctx.switchToWs().getClient();
return client.data?.user;
},
);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// src/events/events.gateway.ts
import { SubscribeMessage, WebSocketGateway } from '@nestjs/websockets';
import { WsUser } from '../common/decorators/ws-user.decorator';
import { UserPayload } from '../common/interfaces/user-payload.interface';
@WebSocketGateway()
export class EventsGateway {
@SubscribeMessage('message')
handleMessage(@WsUser() user: UserPayload, payload: any) {
console.log(`Message from user ${user.id}:`, payload);
return { event: 'message', data: 'Message received' };
}
}
|
デコレータの整理とエクスポート#
プロジェクトが大きくなると、デコレータの管理が重要になります。インデックスファイルを作成して一括エクスポートすると便利です。
1
2
3
4
5
6
7
8
|
// src/common/decorators/index.ts
export * from './user.decorator';
export * from './client-ip.decorator';
export * from './user-agent.decorator';
export * from './pagination.decorator';
export * from './public.decorator';
export * from './roles.decorator';
export * from './auth.decorator';
|
使用時は以下のようにインポートできます。
1
|
import { User, Pagination, Auth, Roles, Public } from '../common/decorators';
|
期待される結果#
本記事で紹介したカスタムデコレータを実装することで、以下の効果が得られます。
コードの可読性向上#
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// Before: 手動でリクエストからデータを抽出
@Get()
getProfile(@Req() request: Request) {
const user = request.user;
const page = parseInt(request.query.page) || 1;
// ...
}
// After: デコレータで宣言的に取得
@Get()
getProfile(@User() user: UserPayload, @Pagination() pagination: PaginationParams) {
// 直接使用可能
}
|
再利用性の向上#
1つのデコレータを複数のコントローラで共有でき、ロジックの重複を排除できます。
型安全性の確保#
TypeScriptの型システムを活用し、デコレータが返す値の型を明確に定義できます。
テスタビリティの向上#
デコレータ単体のテストが可能になり、モックも容易になります。
まとめ#
本記事では、NestJSにおけるカスタムデコレータの作成方法を解説しました。
- パラメータデコレータ:
createParamDecorator()を使用してリクエストからデータを抽出
- メソッドデコレータ:
SetMetadata()を使用してメタデータを設定
- デコレータ合成:
applyDecorators()で複数のデコレータを1つにまとめる
カスタムデコレータを適切に活用することで、NestJSアプリケーションのコードをより宣言的で保守性の高いものにできます。特に、認証・認可、ページネーション、リクエスト情報の抽出など、アプリケーション全体で繰り返し使用するパターンをデコレータ化することで、開発効率を大幅に向上させることができます。
参考リンク#