NestJSのAPIからSwagger Specを出力する

現在、NestJSを使って自社サービスを開発しています。

ふと、こう思うんですね。

「開発メンバーとPostmanのコレクションを共有できたら効率上がりそう」と

Postmanは、Swagger Specファイルからインポートすることでコレクションを簡単に作れます(今までは個人が手作業しててつらみがあった)

というわけで、まずは現在開発しているNestJS製のAPIからSwagger Specファイルを作ってみようというお話です。

# 手順
なんと公式にあります。しかし、ここ1,2年であまり日本語で解説している記事がなかったので、手元で動かしながら書いていきます。

https://docs.nestjs.com/openapi/introduction

## パッケージのインストール
```bash
yarn add @nestjs/swagger
```

## `bootstrap()`に設定を追記
ここで出力に関するオプションを設定できます。設定内容は公式のママです。

```diff_typescript
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
+ import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import * as bodyParser from 'body-parser';

import { TimeoutInterceptor } from './common/interceptors/timeout.interceptor';
import { AppModule } from './modules/app.module';

const WHITE_LIST = ['http://localhost:3000'];

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalInterceptors(new TimeoutInterceptor());
  app.enableCors({
    origin: WHITE_LIST,
  });
  app.use(bodyParser.json({ limit: '50mb' }));
  app.useGlobalPipes(new ValidationPipe({ stopAtFirstError: true, forbidUnknownValues: false }));

+  const config = new DocumentBuilder()
+   .setTitle('Cats example')
+   .setDescription('The cats API description')
+   .setVersion('1.0')
+   .addTag('cats')
+   .build();
+ const document = SwaggerModule.createDocument(app, config);
+ SwaggerModule.setup('api', app, document);

  await app.listen(process.env.APP_PORT || 3000);
}

bootstrap();

```

## Swagger UIを起動する
アプリケーションを起動し
```bash
npm run start
```

(ポートはご自身の環境に置き換えてください)
[http://localhost:3000/api](http://localhost:3000/api)にアクセスすると

現在実装されているAPIの一覧が表示されます(現段階ではタグを付けてない)
![スクリーンショット 2023-07-17 11.26.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/6bd616b1-f0de-b3e6-9084-e5c94fea4ae5.png)

## 本題: JSONをダウンロードする

[http://localhost:3000/api-json](http://localhost:3000/api-json) にアクセスするだけでJSON形式でSwagger Specファイルをダウンロードできます。

とても便利

## Postmanにコレクションとしてインポートする
[Importing and exporting data | Postman Learning Center](https://learning.postman.com/docs/getting-started/importing-and-exporting-data/)
![スクリーンショット 2023-07-17 11.33.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/65c89d72-198c-9a2e-e9b2-29482e54026c.png)

業務関連のAPIなので全ては写しませんが、こんな感じにインポートされます。ヨシ！
![スクリーンショット 2023-07-17 11.34.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/06259f7d-abc4-a245-5a93-cbf9aa671ea8.png)

# オプションを弄っていく

```diff_typescript
  const config = new DocumentBuilder()
    .setTitle('Cats example')
    .setDescription('The cats API description')
    .setVersion('1.0')
    .addTag('cats')
+   .addServer('http://localhost:3001') // 
+   .addGlobalParameters(
+     {
+       name: 'idToken',
+       in: 'header',
+       schema: { default: '{{idToken}}' },
+     },
+     {
+       name: 'authorization',
+       in: 'header',
+       schema: { default: '{{accessToken}}' },
+     },
+   )
+   .build();
```

> これはTipsですが、
> `addGlobalParameters()`でschema.defaultに`{{環境変数}}`と指定することでPostmanの環境変数を参照してます。

各APIにはこんな感じで設定できます。

```diff_typescript
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
+ import {
+  ApiBearerAuth,
+  ApiOperation,
+  ApiResponse,
+  ApiTags,
+ } from '@nestjs/swagger';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
import { Cat } from './entities/cat.entity';

+ @ApiBearerAuth()
+ @ApiTags('cats')
@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
+ @ApiOperation({ summary: 'Create cat' })
+ @ApiResponse({ status: 403, description: 'Forbidden.' })
  async create(@Body() createCatDto: CreateCatDto): Promise<Cat> {
    return this.catsService.create(createCatDto);
  }
```
こんな感じにTagも付与できました
![スクリーンショット 2023-07-17 14.51.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/de5c7961-9f7c-383b-6a1e-baa90c8becf3.png)

SwaggerのSchemaをDTOから作成するには、`@ApiProperty()`デコレータを使います。

```typescript:公式より
import { ApiProperty } from '@nestjs/swagger';

export class Cat {
  /**
   * The name of the Cat
   * @example Kitty
   */
  name: string;

  @ApiProperty({ example: 1, description: 'The age of the Cat' })
  age: number;

  @ApiProperty({
    example: 'Maine Coon',
    description: 'The breed of the Cat',
  })
  breed: string;
}
```

# 課題
現プロジェクトでは、NestJSでバリデーターに使うDTOを、RHFの`classValidatorResolver()`を使ってフォームバリデーションにも使いまわしています。

その場合、`ApiResponse()`などはクライアント(Next.js)での使用が想定されていないので、`next build`がコケるようになってしまいました。

モノレポでDTOを共有している場合はSchemaを生成できないっぽいので注意・工夫が必要です。