こんにちは、OTTサービス技術部 開発第1グループの清瀬です。 主にフロントエンドエンジニアを担当しております。
とあるプロダクトで、NestJSを触る機会がありました。 そのプロダクトではAPIドキュメントがなかったので、NestJSのdecoratorを利用してSwaggerSpec(APIドキュメント)を自動生成してみたので、その手順を紹介したいと思います。
そもそもNestJSって?
NestJSはNode.js上で動く、バックエンドアプリケーションを作成する事が可能なフレームワークです。
TypeScriptで構築されており、普段フロントエンドでTypeScriptを触っている身からしたら、抵抗感なくコードを読むことができました。
そしてNestJSの特徴の1つとして挙げられるのがdecoratorです。NestJSではdecoratorを利用して、APIのルーティングや、ビジネスロジックの実装を行なっていきます。
以下NestJSでのルーティング定義のサンプルです。
@Get('todos/:id') getOne(@Param('id') id: number) { return this.todo.findOne(id); };
この@Get
や@Param
がdecoratorとなります。
@Get
はHTTP GET リクエスト用のルーティングを定義し、
@Param
はパスパラメーターの定義を行うことができます。
このようにdecoratorは、NestJSを制御する上で必要不可欠であり、NestJSの特徴といえます。
より詳細な情報は公式サイトをご確認ください。
APIドキュメント生成手順
ここから実際にSwagger Specの自動生成の手順を紹介していきます。
- @nestjs/swaggerのパッケージを追加します。
yarn add @nestjs/swagger swagger-ui-express
(ここではyarnを使用していますが、環境に合わせて変えてください)
- main.ts(エントリーポイント)の修正
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { const app = await NestFactory.create(AppModule); //************Swaggerの設定************// const config = new DocumentBuilder() .setTitle('documents') //titleの設定 .setDescription('Swagger Document') //Documentの説明 .setVersion('1.0') //APIのバージョンを指定することが可能 .addTag('Swagger Document sample') //Tagの説明 .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('documents', app, document); //第一引数に指定したものが、ドキュメントページのURLとなります。 //************************************// await app.listen(3000); } bootstrap();
アプリケーション起動後にアプリケーションのエンドポイントに/documents/をつけてブラウザで表示させると以下の画面が表示されます。
http://localhost:3000/documents/
今回はすでにControllerファイルでルーティングの処理を定義しているので、エントリーポイントにswaggerの設定を入れるだけで、自動でルーティング等の情報を生成してくれます。
(ルーティングの処理が未実装であっても、Swaggerの画面自体は表示されます。)
しかし、APIの説明や、パスパラメーターの説明などは別途@nestjs/swaggerのdecoratorで定義する必要があります。
@nestjs/swaggerのdecorator紹介
基本的にAPIの説明などは、Controllerに記載していきます。
先に実装例を出しておきます。
@Get('todos/:id') @ApiOperation({summary: 'TODOの取得', description: '指定されたIDのTODOを取得する'}) @ApiResponse({ status: HttpStatus.OK, type: GetResponseDto, description: '成功時のレスポンス' }) @ApiParam({ name: 'id', description: '取得対象のTODOに紐付くID', example: 3, type: Number }) getOne(@Param('id') id: number) { return this.todo.findOne(id); };
@ApiOperation
引数: (summary: string, description: string)
APIの説明を記載することが出来ます。
@ApiParam
引数: (name: string, description: string, required: boolean, type: any, example: any)
パスパラメーターについて記載する事が出来ます。
@ApiResponse
引数: (status: number, description: string, type: any)
API のレスポンスについて記載する事が出来ます。
@ApiResponse({ status: HttpStatus.OK, type: GetResponseDto, description: '成功時のレスポンス' })
typeにはレスポンスの型を定義する事ができ、DTO等を指定することで実際のレスポンスの形式がSwagger UI上に表示されるようになります。
@ApiProperty
引数: (name: string, description: string, required: boolean, type: any, example: any, default: any)
このdecoratorは、DTOクラス(Data Transfer Object)のプロパティを定義することが出来ます。
@ApiResponse
でのtypeで指定した時に、レスポンスの例や、プロパティの説明が表示される様になります。
実装の例が以下になります。
export class RequestBodyDto { @IsString() @ApiProperty({ required: true, description: 'TODOのタイトル', example: '明日はジムに行こう' }) title: string; @IsString() @ApiProperty({ required: true, description: 'TODOの内容', example: 'ベンチプレスをやる' }) content: string; } export class GetResponseDto extends RequestBodyDto { @IsNumber() @ApiProperty({ description: '取得対象のTODOに紐付くID', example: 3 }) id: number; @IsString() @ApiProperty({ description: '作成日時', example: '2025-03-24T01:11:08.000Z' }) created_at: string; @IsString() @ApiProperty({ description: '更新日時', example: '2025-03-25T12:11:08.000Z' }) updated_at: string; }
【Tips】カスタムデコレーターでエラーレスポンスを共通化しよう
API1つに対して、HTTPステータスコードごとにレスポンスを定義すると以下のようになります。
@Get('todos/:id') @ApiResponse({ status: HttpStatus.OK, type: GetResponseDto, description: 'Success' }) //*それぞれのAPIの定義に記載するのは非効率*// @ApiResponse({ status: HttpStatus.BAD_REQUEST, type: ErrorResponseDto, description: 'Bad Request' }) @ApiResponse({ status: HttpStatus.NOT_FOUND, type: ErrorResponseDto, description: 'Not Found' }) @ApiResponse({ status: HttpStatus.INTERNAL_SERVER_ERROR, type: ErrorResponseDto, description: 'Internal Server Error' }) //************************************// @ApiParam({ name: 'id', description: '取得対象のTODOに紐付くID', example: 3, type: Number }) getOne(@Param('id') id: number) { return this.todo.findOne(id); };
エラーレスポンスが多くあり、これをAPIの定義1つ1つに記載していくのは非効率かつ、可読性も落ちていきます。
なので、NestJSのapplyDecoratorsを利用して、エラーレスポンスを共通化する方法も紹介します。
まず@ApiResponse
を返すメソッドを定義します。
export function ApiBadRequestResponse() { return ApiResponse({ status: HttpStatus.BAD_REQUEST, description: '不正なリクエスト', schema: { type: 'object', properties: { statusCode: { type: 'number', example: 400, }, message: { type: 'array', items: { type: 'string', example: '不正パラメーターのエラーメッセージが表示される', }, }, error: { type: 'string', example: 'Bad Request', }, }, }, }); }
定義したメソッドをapplyDecorators
に渡します。
export function GetApiErrorResponse() { return applyDecorators(ApiBadRequestResponse(), ApiNotFoundResponse(), ApiInternalErrorResponse()); }
これをControllerファイルで呼び出します。
以下の例の場合Controllerに記載されているAPI全て同じエラーレスポンスを返すということになります。APIごとにエラーレスポンスが違う場合は、ルーティング処理ごとにカスタムデコレーターを呼び出す形にしてください。
@Controller() @GetApiErrorResponse() export class AppController { }
これで各APIにエラーレスポンスを定義しなくても、Swagger UI上でエラーレスポンスが表示されます。
まとめ
@nestjs/swaggerを利用することで、簡単にAPIドキュメントが作成する事ができました。また、実際のAPIの定義している箇所に記載していくので、ドキュメントの保守がしやすいというメリットがあると思います。 その分SwaggerUIに表示させるために実際のAPIの機能とは関係ない処理を同じ箇所に記載して行かなければならないのがデメリットともいえそうです。
閲覧ありがとうございました。