13
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

kurogoma939のひとりアドベントカレンダーAdvent Calendar 2024

Day 9

実装からswaggerを書き起こす(NestJS)

Last updated at Posted at 2024-12-08

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

APIのドキュメントといえばswagerが有名だと思いますが、swaggerも雑に書いては意味を為しません。
例えば、以下のような状態はフロントエンドエンジニアにとってはswagegrの価値が下がってしまいます。

  • レスポンスモデルがない
  • レスポンスのサンプルもない
  • requiredなパス・パラメーターに一体何を指定したらいいかわからない

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

NestJSでの導入方法

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

$ npm install --save @nestjs/swagger

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

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というモデルも用意しましたが、こちらも扱いに注意が必要です。

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

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

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

スクリーンショット 2024-12-08 0.01.29.png

2. @ApiQuery

クエリパラメーターについては

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

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

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

3. @ApiResponse

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

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

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

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

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

13
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?