OpenAPI・Swaggerでインタラクティブな API 仕様ドキュメントを作成する

# 初めに

今回はOpenAPIを用いたAPI設計書の書き方とSwagger(UI)の使い方についてまとめていきます。
この記事の目標はタイトルの通り、OpenAPI仕様に則ったAPI定義を作成し、SwaggerからそのAPI仕様を確認できることです。
API設計などについては省いており、あくまで**OpenAPI仕様の書き方についての解説記事**になります。

## OpenAPIとは

RESTful APIのインターフェースを記述するための**フォーマット**です。
定められたフォーマットに則ってAPIの構造と動作を明確に定義することで
開発者はAPIのエンドポイント、パラメータ、レスポンスなどを正確に文書化し、共有することができます🚀

## Swaggerとは

Swaggerは、OpenAPI仕様を視覚化し、対話式のAPIドキュメントを提供するための一連のツールです。
以下のようなものがあります。

| ツール | 概要 |
| ---- | ---- | 
| Swagger Editor | オンラインツールでOpenAPI仕様に基づいたAPIの記述と検証を行うために使用されます。 | 
| Swagger Codegen | OpenAPI仕様からクライアントライブラリ、サーバースタブ、APIドキュメントを自動生成するためのツールです。 |
| Swagger UI | OpenAPI仕様に基づいて記述されたAPIを視覚化し、対話的にドキュメントを提供するWebベースのUIツールです。 |

今回は、vscodeの拡張機能[Swagger Viewer](https://marketplace.visualstudio.com/items?itemName=Arjun.swagger-viewer)を用いてブラウザで確認します。
この拡張機能はSwagger UIに相当する画面表示を行なってくれます。

https://marketplace.visualstudio.com/items?itemName=Arjun.swagger-viewer

インストール後、拡張機能設定でswaggerを検索し、Preview In Browserを有効にすると
ブラウザで確認できるようになります。
![](https://storage.googleapis.com/zenn-user-upload/8c0cee75f82d-20231217.png)

## サンプルAPI定義の作成と、表示

OpenAPIの記述方法は`JSON`か`YAML`の2パターンがあり、一般的には`YAML`形式が多いようなので、本記事でも`YAML`形式で記述します。

:::note info
YAML
YAMLはわかりやすいデータシリアライズ (serialize) 言語です。
設定ファイルの記述に使用されることが多く、改行とインデントで、データ構造を表します。
:::

vscodeでymlファイルを作成し、サンプルとして下記コードを貼り付けます。
`GET /message`で実行するとメッセージが返ってくるAPI定義になります。
現時点で、コードの意味がわからなくても後ほど解説するので大丈夫です！

```rest-api.yml
openapi: "3.0.3"

info:
  title: "Sample API"
  version: "1.0.0"

paths:
  "/message":
    get:
      summary: "メッセージ取得"
      description: "メッセージを取得します"
      responses:
        "200":
          description: "Success operation"
          content:
            application/json:
              schema:
                type: string
                example: "Hello World!!"
```

「Shift」+「Cmd」+「P」でコマンドパレットを開きPreView Swaggerを入力、選択するとブラウザでViewerが立ち上がり、API仕様が確認できました🎉

![スクリーンショット 2023-12-17 11.31.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/f9467255-f30c-ce0c-ef69-2c77922a13f9.png)

以上が、OpenAPIに則ったAPI定義ファイルを作成し、Swaggerで確認する流れです！

## OpenAPIの書き方

ここからOpenAPIに則った`YAML`ファイルの書き方について解説します。

### ルートオブジェクト

主要なオブジェクトは下記の7種類です。
必須項目は`openapi`,`info`,`paths`の3種類で、このオブジェクトさえ記述していれば最低限のopenAPI仕様書としては一応機能します。

```rest-api.yml
openapi: "3.0.3"

info:
...

servers:
...

tags:
...

paths:
...

security:
...

components:
...
 
```

| オブジェクト | 概要 |
| ---- | ---- | 
| openapi | **必須項目。** 使用しているOpenAPIのバージョンを指定します。 | 
| info | **必須項目。** APIの基本情報（タイトル、バージョン、説明等）を定義します。 |
| servers | APIがホストされているサーバーのURLを指定し、APIの実行可能な環境を示します。 |
| tags | APIを分類するタグを定義します。 |
| paths | **必須項目。** APIの具体的なエンドポイントと、それらに対する操作（GET, POST等）を定義します。 |
| security | API全体に適用されるセキュリティスキーム（APIキー、OAuth2等）を定義します。 |
| components | OpenAPIの中で、再利用可能なパラメータ、スキーマなど、様々なオブジェクトをコンポーネントとして定義し、API定義全体で使用できるようにします。 |

### infoオブジェクト

`info`オブジェクトは、APIの基本情報（タイトル、バージョン、説明等）を定義します。
必須なオブジェクトは`title`,`version`の2つです。
`version`は、このドキュメントのバージョン情報であることに注意してください。
OpenAPIのバージョン指定は、先述している通り`openapi`オブジェクトで記述します。
`description`ではマークダウン形式の記述も可能です✍🏻

:::note info
`termsOfService`,`contact`,`license`は、基本的に世間に公開するようなAPIを定義をする際に必要になります。
社内ドキュメントとしては不必要である場合が多いです。
:::

```yml
info:
  title: "Sample API" # 必須。APIのタイトル
  version: "1.0.0" # 必須。APIのドキュメントのバージョン情報
  description: "このAPIはサンプルの操作とデータを提供します。" # APIの短い説明
  termsOfService: "http://example.com/terms/" # APIの利用規約へのURL
  contact: # APIの連絡先情報
    name: "APIサポート" # 連絡先の名前
    url: "http://www.example.com/support" # 連絡先のURL
    email: "support@example.com" # 連絡先のEメールアドレス
  license: # APIのライセンス情報
    name: "Apache 2.0" # ライセンスの名前
    url: "https://www.apache.org/licenses/LICENSE-2.0.html" # ライセンスURL
```

上記infoオブジェクトを記述し、Swagger Viewerで見てみると下記の通りの表示になります。

![スクリーンショット 2023-12-23 14.41.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/9bce72c7-196a-d84b-5449-78d0a27abb36.png)

### serverオブジェクト

`server`オブジェクトはAPIがホストされているサーバーのURLを指定し、APIの実行可能な環境を示します。配列で定義します。

:::note info
YAMLでは`-`を行頭に置くことで配列の要素を定義します。
:::

```yml
servers:
  - url: "https://api.example.com/v1" # APIをホストしているサーバーのURL
    description: "本番環境サーバー" # このサーバーに関する追加情報
  - url: "https://staging-api.example.com" # 別のサーバーのURL、例えばステージング環境
    description: "ステージング環境サーバー" # このステージングサーバーに関する追加情報
  - url: "http://localhost:5000" # ローカル開発用のサーバーURL
    description: "開発環境サーバー" # この開発サーバーに関する追加情報
```

![スクリーンショット 2023-12-23 17.22.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/91c1a33f-8218-1420-4c1e-7a98417d165e.png)

`servers`オブジェクト内では、`variables`というオブジェクトを使用することもできます。
この`variables`オブジェクトは、サーバーURL内の一部をパラメーター化し動的に置き換えるために使用されるパラメーターのセットです。

```yml
servers:
  - url: "https://{environment}.example.com/v1" # 動的な部分（{environment}）を含むサーバーURL
    description: "APIサーバー - 環境によって異なります"
    variables: # URLの動的部分を定義するための変数
      environment: # 変数の名前。urlの{environment}部分
        default: "dev" # 変数のデフォルト値。この例では'default'を'dev'としています
        description: "APIをホストする環境。例えば、'dev', 'staging', 'prod'など。" # 変数に関する説明。
        enum: ["dev", "staging", "prod"] # 変数に許可される値のリスト。
```

Servers部分に、現在のURLを表すComputed URLや、指定した変数がオプションにあるセレクタが追加されていることがわかります。

![スクリーンショット 2023-12-23 17.45.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/0ed60cb1-0a8d-117b-a46d-177638984fc0.png)

### pathsオブジェクト

`paths`オブジェクトはAPI定義の中心的な部分であり、APIのエンドポイントとそれらに対する具体的な操作(HTTPメソッド）を定義します。

```yml
paths:
  /items: # エンドポイント
    get: # HTTPメソッド
      summary: "アイテム一覧の取得" # 操作の簡単な説明
      description: "利用可能なアイテムの一覧を返します。" # 操作の詳細な説明
      tags: ["items"] # タグの付与。ルートオブジェクトで定義したタグ、または任意のタグが付与可能
      deprecated: false # 廃止されたAPIかどうか。trueにするとグレーアウトされた表示になり廃止済であることを明示できる
      parameters: # APIリクエストのパラメータを定義するセクション
        ...
      requestBody: # リクエストボディを定義
        ...
      responses: # レスポンスを定義
        ...
      
```

patshオブジェクトの中でも、`parameters`、`requestBody`、`responses`にはさらに細かく
オブジェクトが用意されていますので、詳しくみていきます。


#### parametersオブジェクト

リクエストにパラメーターが必要な場合に記述します。


```yml
  /items/{itemId}:
    get:
      summary: "特定のアイテムの詳細情報を取得"
      description: "指定されたIDを持つアイテムの詳細情報を返します。"
      tags: ["items"]
      parameters: # APIリクエストのパラメータを定義するセクション
        - name: itemId # パラメータのプロパティ名
          in: path # パラメータの位置: パスパラメータ
          required: true # 必須パラメータかどうか
          description: "取得するアイテムのID。" # パラメータの説明
          schema: { type: string, example: "1021" } # パラメータの型と例。このようにインライン記法もできる
      requestBody:
        ...
      responses:
        ...
```

Parametersというセクションで、定義したパラメーターの内容が表示されます。

![スクリーンショット 2023-12-23 19.11.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/7b8f3ca2-4470-5ac4-2762-e6b03430713b.png)

#### requestBodyオブジェクト

APIにデータを送信する際に使用されるリクエストボディの内容を定義するために使われます。
これは主にPOST、PUTなどのメソッドで利用されます。
下記では、POSTメソッドの定義を追加しました。

```yml
paths:
  /items:
    get:
      ...

    post:
      summary: "新しいアイテムを作成"
      description: "新しいアイテムを追加します。"
      tags: ["items"] 
      requestBody: # リクエストボディを定義
        required: true # このリクエストでボディが必須
        content:
          application/json: # リクエストボディのメディアタイプ
            schema: # リクエストボディの型を定義
              type: object # リクエストボディの型: オブジェクト
              properties: # オブジェクトのプロパティを定義
                name: # プロパティ名を記述
                  type: string # アイテム名の型: 文字列
                  description: "アイテムの名前。" # プロパティの説明
                description: # プロパティ名を記述
                  type: string # アイテム説明の型: 文字列
                  description: "アイテムの詳細説明。" # プロパティの説明
            example: # リクエストボディの例
              name: "アイテム1"
              description: "アイテム1の説明が入ります"
      responses:
        ...

  /items/{itemId}:
    get:
      ...

```

上記のように記述すると下記のようなRequest bodyが表示されます。

![スクリーンショット 2023-12-27 21.51.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/e28b43f4-92c7-88ad-1c72-6c85b29b2418.png)


#### responsesオブジェクト

レスポンスを定義していくオブジェクトです。
ステータスコードごとにオブジェクトを用意します。

例

```yml
...
responses:
    "201":
        description: "新しいアイテムを作成しました。" # レスポンスの説明
        headers: # レスポンスヘッダー(レスポンスに特殊なヘッダーがあれば記載)
        ...
        content: # レスポンスボディ
        ...
    "400":
        description: "不正なリクエスト。入力が無効です。"
        ...

```

中でもcontentオブジェクトはレスポンスの中身を定義していくオブジェクトで、
一通り記述した例は下記の通りです。

```yml
paths:
  /items: 
    get:
      summary: "アイテム一覧の取得"
      description: "利用可能なアイテムの一覧を返します。"
      tags: ["items"] 
      deprecated: false
      responses: # レスポンスを定義
        "200": # HTTPステータスコード 200の場合の処理をこのセクションで定義
          description: "成功。アイテム一覧を返します。" # この応答の説明
          content: # レスポンスデータを定義
            application/json: # レスポンスのコンテントタイプ JSON形式
              schema: # レスポンスのスキーマを定義するセクション
                type: array # レスポンスの型: 配列
                example: # スキーマの例を定義できるセクション
                  - id: "1"
                    name: "アイテム1"
                  - id: "2"
                    name: "アイテム2"
                items: # 配列内のアイテムを定義するセクション
                  type: object # アイテムの型: オブジェクト
                  properties: # オブジェクトのプロパティを定義するセクション
                    id: { type: string, description: "アイテムのID。" } # 型と説明
                    name: { type: string, description: "アイテムの名前。" } # 型と説明
        "400": # HTTPステータスコード 400の場合の処理をこのセクションで定義
          description: "不正なリクエスト。パラメータが無効です。" # この応答の説明
```

![スクリーンショット 2023-12-30 12.24.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/dcd9ed7b-999e-bc22-4283-fea54d46b1c8.png)


### tagsオブジェクト

API定義を分類し、整理するために使用されます。
各操作を論理的なグループに分類して、ドキュメント内でのナビゲーションを容易にします。
前章のpathsオブジェクト内にもすでに登場しているtagsオブジェクトですが、ルートオブジェクトでも定義できます。

例） ルートオブジェクトで定義
```yml
openapi: "3.0.3"

info:
...

servers:
...

tags:
  - name: "items"
    description: "アイテムに関連する操作。アイテム取得、作成、更新、削除などでが含まれます。"
  - name: "users"
    description: "ユーザー管理に関連する操作。ユーザーの作成、プロファイルの取得、更新などが含まれます。"

paths:
...

security:
...

components:
...
```

tagsを未使用の状態だと全てもAPIがdefaultというタイトルの折りたたみメニューに格納されていますが、上記のように定義することで、折りたたみメニューが追加されます📝

![スクリーンショット 2023-12-30 13.32.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/2cd68030-1c9d-dd9e-97c5-0a2ec72ba951.png)

そして下記のようにpatshオブジェクト内で、そのAPI操作に適切なtagsを指定することで、
ルートで定義していた折りたたみメニュー内に集約される形です。

```yml
paths:
  /items:
    get:
      tags: ["items"]
      ...
    post:
      tags: ["items"]
      ...
```

![スクリーンショット 2023-12-30 13.31.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/0d883f64-af9c-b46c-fba6-8f93512f1758.png)

### componentsオブジェクト

API定義内のさまざまなスキーマ、レスポンス、パラメータ、セキュリティスキームなどの再利用可能なコンポーネントを格納するために使用されます。
これにより、重複する記述を減らし一貫性を高め、API定義のメンテナンスを容易にします。
主に使用される5つの要素は下記の通りです。

```yml
openapi: "3.0.3"

info:
...

servers:
...

tags:
...

paths:
...

security:
...

components:
  schemas: # スキーマ定義: リクエストとレスポンスで使用される再利用可能な型を定義します
    ...
  responses: # レスポンス定義: 再利用可能なレスポンスオブジェクトを定義します
    ...
  parameters: # パラメータ定義: 再利用可能なパラメータオブジェクトを定義します
    ...
  requestBodies: # リクエストボディ定義: 再利用可能なリクエストボディオブジェクトを定義します
    ...
  SecuritySchemas: # セキュリティスキーム定義: APIで使用されるセキュリティスキームの詳細を定義します
    ...
```

下記のAPI定義では、componentsオブジェクトを使用した例で作成しています。
具体的には、schemas、responsesオブジェクトを定義し、各API操作で、コンポーネントを参照し再利用しています。
コンポーネントを利用するときは、`$ref: "#/components/schemas/Error"`のような記述で利用したいコンポーネントを指定し、参照します。

```yml
openapi: "3.0.3"

info:
  title: "サンプルAPI定義"
  version: "1.0.0"
  description: "Componentsオブジェクトの使用例を確認するための簡易的API定義"

components: # コンポーネントを定義
  schemas: # 再利用したいスキーマを定義
    Item: # 具体的なスキーマ名とその内容
      type: object
      properties:
        id:
          type: string
          description: "アイテムの一意識別子"
        name:
          type: string
          description: "アイテム名"
    Error: # 具体的なスキーマ名とその内容
      type: object
      properties:
        code:
          type: integer
          format: int32
          description: "エラーの種類を表すエラーコード"
        message:
          type: string
          description: "エラー詳細メッセージ"
  responses: # 再利用したいレスポンスを定義
    ItemNotFound:
      description: "Item not found."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error" # componentsオブジェクト内で定義したErrorスキーマを参照先に指定

paths:
  /items:
    get:
      summary: "アイテム一覧を取得する"
      responses:
        "200":
          description: "アイテム一覧"
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Item" # componentsオブジェクトの中で定義したItemスキーマを参照先に指定

    post:
      summary: "アイテムを追加する"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Item"
      responses:
        "201":
          description: "アイテム追加"

  /items/{itemId}:
    get:
      summary: "特定のアイテムを取得"
      parameters:
        - name: itemId
          in: path
          required: true
          description: "アイテムの一意識別子"
          schema:
            type: string
      responses:
        "200":
          description: "アイテム詳細情報"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Item" # componentsオブジェクトの中で定義したItemスキーマを参照先に指定
        "404":
          $ref: "#/components/responses/ItemNotFound" # componentsオブジェクトの中で定義したItemNotFoundレスポンスを参照先に指定
```

下記の通り、レスポンスのSchemaや404のレスポンスなど、参照先に指定したコンポーネントの内容が表示されており、重複した記述をせず再利用可能であることがわかります🙌

![スクリーンショット 2023-12-30 14.36.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/97bc55a3-983a-eeb2-99be-2f33464696e5.png)

### securityオブジェクト

APIへのアクセスを制御するためのセキュリティスキームを定義します。
これにより、APIがどのようにユーザー認証を要求するか、どのような認証方法を使用するかを文書化できます。
`components`オブジェクトに`securitySchemes`というオブジェクトが登場しましたが、
OpenAPI仕様において、ルートレベルの`security`オブジェクトは通常、`components`オブジェクト内の`securitySchemes`セクションで定義されたセキュリティスキームを参照するために使用されます。

前章で作成したサンプルコードにルートオブジェクトの`security`と、`components`オブジェクト内の、`securitySchemes`を追記します。
`ApiKeyAuth`という名前のAPIキーセキュリティスキームを定義し、API全体に適用しています。

```yml
...

security: # グローバルセキュリティ要件: このAPIのすべての操作に適用されます
  - ApiKeyAuth: [] 

components:
  ...
  securitySchemes:
    ApiKeyAuth: # セキュリティスキームの名前
      type: apiKey
      in: header # APIキーがヘッダーに含まれる
      name: X-API-KEY # ヘッダーの名前
```

キーアイコンなどが追加され、クライアントはX-API-KEYヘッダーに適切なAPIキーを提供する必要があることを明示しています。

![スクリーンショット 2023-12-30 15.11.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/30673d79-b040-5626-430b-4fb25f7362d7.png)

グローバルセキュリティ要件が不要で、POSTなど特定の操作時に、API key認証を要求する場合は、ルートの`security`オブジェクトは削除し、特定操作のAPI定義に`security`オブジェクトを追記します。

```yml
    ...
    post:
      summary: "アイテムを追加する"
      requestBody:
        ...
      responses:
        ...
      security:
        - ApiKeyAuth: []
```

![スクリーンショット 2023-12-30 15.23.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/a3407fc2-29b0-2eb2-e1ea-4ebc66d73e72.png)


## まとめ

今回はOpenAPI、SwaggerでのAPI定義の記述法についてまとめましたがいかがだったでしょうか。
OpenAPI、SwaggerでAPI定義を作成することで、ファイルに記述した内容がインタラクティブなUIとして確認できるだけではなく、Gitでのバージョン管理やコンポーネントでの再利用性など、効率よく作成、管理できます。