実装からswaggerを書き起こす（NestJS）


SSKDsのAPIを開発する場合（に限ったわけではないですが）、フロントエンドエンジニアへ素早くドキュメントを提供することは開発においてとても重要です。

APIのドキュメントといえばswagerが有名だと思いますが、swaggerも雑に書いては意味を為しません。
例えば、以下のような状態はフロントエンドエンジニアにとってはswagegrの価値が下がってしまいます。
- レスポンスモデルがない
- レスポンスのサンプルもない
- requiredなパス・パラメーターに一体何を指定したらいいかわからない

ここ最近は、[openai generator](https://github.com/OpenAPITools/openapi-generator)のようにswaggerからretrofit関連であるエンドポイントとリクエスト・レスポンスモデルを一括生成してしまう仕組みも使われていることからswaggerの重要度は上がっていると思います。

## NestJSでの導入方法

NestJSはTypeScriptのサーバーサイドフレームワークです。
Swaggerについては公式ドキュメントにも記載があります。

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

```bash
$ npm install --save @nestjs/swagger
```

NestJSはクラス + アノテーションで定義をしていきます。
以下がサンプルとなります。

```ts: sample.controller.ts
@Authorized()
@Controller('samples')
export class SampleController extends BaseController {

  constructor(
    private readonly sampleService: SampleService,
  ) {
    super()
  }

  get errorCodes() {
    return SampleController.ERROR_CODES
  }

  // ここからswagger情報
  @ApiOperation({
    description: 'サンプル一覧を取得する',
    tags: ['sample'],
  })
  @ApiQuery({
    name: 'type',
    required: false,
    description: '対象の種類 (user, staff, all) 未指定の場合はallが選択される',
  })
  @ApiResponse({
    status: 200,
    description: '正常時',
    type: GetSamplesResponse,
  })
  // ここまでswagger情報
  @Get('')
  async getSamples(@Req() request, @Query('type') type: string): Promise<GetSamplesResponse>{
    // typeがnull or undefinedの場合はallを設定
    if (type === null || type === undefined) {
      type = 'all'
    }
    data = await this.sampleService.getSamples()
    const result: GetSamplesResponse = {
      result: data
    }
    return result
  }
}
```

また、サンプルとして`GetSamplesResponse`というモデルも用意しましたが、こちらも扱いに注意が必要です。

```ts: sample.dto.ts
export class GetSamplesResponse {
  // @ApiPropertyもswaggerで表示するための項目です
  @ApiProperty({ type: [SampleData] })
    result: SampleData[]
}

export class SampleData {
  @ApiProperty() id: number
  @ApiProperty() name: string
  @ApiProperty() created_at: Date
  @ApiProperty() updated_at: Date
}
```

では、上記のチェックポイントをそれぞれ記載します。

### 1. @ApiOperationのtag

```ts
  @ApiOperation({
    description: 'サンプル一覧を取得する',
    tags: ['sample'],
  })
```

こちらはswagger上のグループ分けに必須です（図で言う_sample）

![スクリーンショット 2024-12-08 0.01.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/735195/39b4262f-616e-c76c-408c-231ab4d68fa9.png)


### 2. @ApiQuery

クエリパラメーターについては
- 項目が不明なためdescriptionで補助
- この項目を省略した場合クエリパラメーターなのにswagger上でrequired扱いになってしまう

という意図で実装すべき項目です。

```ts
  @ApiQuery({
    name: 'type',
    required: false,
    description: '対象の種類 (user, staff, all) 未指定の場合はallが選択される',
  })
```

### 3. @ApiResponse

こちらは、設定をしないとレスポンスが空になってしまい、実際にAPIを実行しないとどういうレスポンスが帰ってくるかの理解ができないため必須で実装すべき項目です。


```ts
  @ApiResponse({
    status: 200,
    description: '正常時',
    type: GetSamplesResponse,
  })
```

ただし、こちらは2点注意点が必要です。

- 型キャストが曖昧な実装だと全てレスポンスがstringになってしまう
（ただしこちらはNestJSのレスポンス自体もそうなのでSwaggerというよりNestJSの都合です）
- モデルの中にモデルがある場合、@ApiPropertyに設定が必要
こちらは先ほどサンプルで出した通りです。
`GetSamplesResponse`の中で別のモデル（`SampleData`）を用いる場合には`type`の指定をしないとswaggerでうまく表示されません。（そもそもうまく出力されないか、全て型がstringになってしまう）

```ts: sample.dto.ts
export class GetSamplesResponse {
  // @ApiPropertyもswaggerで表示するための項目です
  @ApiProperty({ type: [SampleData] })
    result: SampleData[]
}

export class SampleData {
  @ApiProperty() id: number
  @ApiProperty() name: string
  @ApiProperty() created_at: Date
  @ApiProperty() updated_at: Date
}
```

以上のように設定することでswaggerを見るとどういう形式のものが返却されてくるかが伝わりやすくなります。
今はサンプルとして200の正常系しかないですが、本来余力があれば400, 500系についても記述があると丁寧です。

![スクリーンショット 2024-12-08 0.15.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/735195/b7b16732-13aa-adb5-c5c7-258b2853be09.png)

以上です！
他のサーバーサイドフレームワークにもswagger系のものはあると思うので気になった方はそれぞれのライブラリ等を調べてみてください！