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';
}

@Pagination()デコレータ

ページネーションパラメータを一括取得するデコレータです。

 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()を使用してメタデータを設定するパターンがよく使われます。

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アプリケーションのコードをより宣言的で保守性の高いものにできます。特に、認証・認可、ページネーション、リクエスト情報の抽出など、アプリケーション全体で繰り返し使用するパターンをデコレータ化することで、開発効率を大幅に向上させることができます。

参考リンク