swaggerでAPIの仕様書を書こう!

みなさん、swaggerを使ったことがありますか？

私自身、最近swaggerを使うようになってきたので、今回はswaggerについてまとめてみようと思います。

# swaggerとは

`OpenAPI Specification`(OAS)と呼ばれる、RestfulAPIを構築するためのオープンソースのフレームワークのことです。

昔は、wordやexcelでAPIの仕様書が書かれていましたが、近年ではswaggerが一般化してきたそうです。

## OpenAPIとは

APIの仕様を記述するためのフレームワークやツールです。

OpenAPIは、ソフトウェアアプリケーション同士がやり取りするAPIの仕様書のフォーマットを標準化して、簡単に作成するために作られました。

## RestfulAPIとは

RestfulAPIとは、`REST`と呼ばれる考え方に沿ったAPIのことを指します。

RESTAPIという言い方もありますが、RestfulAPIとRESTAPIは同じものと考えても大丈夫そうです。


## RESTとは

`Representational State Transpher`の略で、分散システムにおいて複数のソフトウェアが連携しやすくするための設計思想(アーキテクチャスタイル)のことを指します。

このRESTには色々原則があるのですが、今回は6原則だけピックアップしていこうと思います。

### クライアント/サーバー
RESTでは、クライアント/サーバーという形式をとっています。
クライアントがリクエストを送ることで、それに対してサーバーが処理をしてクライアントにレスポンスを返すという形式です。


### ステートレスサーバー
ステートレスとは、状態を保持しないことを指します。

つまり、ステートレスサーバーとはサーバーはクライアントアプリケーションの状態を保持しないことです。

ただ例外として、最小限のCookieを使用してステートフルなサーバーとして扱うこともあります。

### キャッシュ
キャッシュとは、一度クライアントがサーバから取得したリソースを使い回す方式です。

### 統一インターフェース
URIで指定したリソース操作を統一した限定的なインターフェースで行うことです。

この説明だけだと分かりにくいですが、APIを定義するときにHTTPメソッドは何でJSONの構造はどうするかということを決めると思います。
これを決めることで特定の機能を持ったAPIの叩く方式を一つにすることができ、これを統一インターフェースと言います。

### 階層化システム
階層化システムとは、webサーバーより前にロードバランサーを置いて負荷分散を行ったり、プロキシをおいてファイアーウォールの役割を行ったり、クライアントからみてサーバーに到達するまでに色々な階層を渡っていくような構造になっていることを指します。

### コードオンデマンド
プログラムコードをサーバーから取得して、クライアントで動かす形式のことです。
つまり、動的にクライアントアプリケーションのコードを生成して、柔軟に動作させることができます。

# swaggerを使うメリット

メリットとしては、先ほど紹介した***RESTの統一インターフェースを共通認識にできる***のが大きいです。

アプリケーションにおいて、APIの仕様を共有するのはとても難しいです。
特に見た目の面でURIがどれでリクエストボディがどれでレスポンスボディがどれでというのを共通認識として共有するのは至難の技です。

APIの仕様を書く場所や書き方をサーバーサイド側でしっかり共通認識を持っていないと、APIがバラバラのとこで定義され、フロント側がサーバーサイドのコードを読まなければならないという、フロント側がサーバーとの通信の実装でとても苦労することになります。

swaggerを使うことでOpenAPIのAPI定義仕様を楽に定義、そして分かりやすく共有することができるので、開発効率を上げることができます。

# swaggerを使ってみる

## 執筆環境を整える

swaggerは、vscodeや各種IDEの拡張機能で簡単にプレビューすることが可能です。

### VSCode

vscodeでは下のOpenAPI(SwaggerEditor)を使用します。

https://marketplace.visualstudio.com/items?itemName=42Crunch.vscode-openapi

VSCode上の右上のSplitボタンに虫眼鏡のマークが書かれているボタンを押します。
![スクリーンショット 2024-10-04 13.55.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3766230/6b0fcbda-ae05-22aa-65ba-ea8defd8d3c0.png)

そうすることでプレビューすることができます。
![スクリーンショット 2024-10-04 13.57.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3766230/0eddeb46-29b4-53a5-cee3-e5e18b030a16.png)


### neovim

neovimでは、swagger-preview.nvimというプラグインを入れます。

https://github.com/vinnymeller/swagger-preview.nvim


READMEを見ながら、各種の設定に合わせて使ってみてください。

一応自分はAstroNvimを使っていて、下のように設定しました。

```swagger-preview.lua
return {
  "vinnymeller/swagger-preview.nvim",
  config = function()
    require("swagger-preview").setup {
      -- The port to run the preview server on
      port = 8000,
      -- The host to run the preview server on
      host = "localhost",
    }
  end,
}
```

### Jetbrains系のIDE
openapi-swagger-editorというプラグインを入れます。

https://plugins.jetbrains.com/plugin/14837-openapi-swagger-editor

`settings`から下の画像のような場所からインストールすることができます。インストールしたら`Apply`を必ず押してください

![スクリーンショット 2024-11-04 3.27.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3766230/e97bb0d2-31a0-c64a-d986-29a07c9b4bdd.png)

### その他(SwaggerEditor)
また、エディタの拡張機能以外にも、[SwaggerEditor](https://editor-next.swagger.io/)というオンラインエディタでは、環境が手元になくてもプレビューができるので特におすすめです。

https://editor-next.swagger.io/

## swaggerのファイルを作成する

swaggerのファイル拡張子は、`.swagger.yml`となります。

例えば、todoアプリのswaggerファイルを作るとなったら

```yml
todo.swagger.yml
```

となります。

## swaggerの書き方を学ぶ

### OpenAPIのversion
まず、使用するOpenAPIのversionを記述します。
```yml
openapi: 3.0.0
```

### Info
Infoでは、タイトルや説明などのAPI全体における情報を記述します。
```yml
info:
    title: [titleを記述]
    description: [説明を記述]
    version: [versionを記述
```

### tags

tagsでは、URIをカテゴリとして分けることができます。

タグは下のように宣言します。

```yml
tags:
  - name: [タグの名前を記述]
    description: [タグの説明を記述]
```


### paths
pathsでは、エンドポイントの指定やHTTPのメソッドはGETなのかPOSTなのかを記述します。



```yml
paths:
  /:
    get:
      summary: 概要
      description: 説明
      tags:
        - タグ名
```

上の場合だとエンドポイントは`/`となり、HTTPのメソッドはGETリクエストでの場合を指定しています。

`summary`では、概要を記述することができます。

`description`では、説明を記述することができます。

また、`tags`で先ほど作成したタグをアタッチして、カテゴライズすることができます。

:::note
HTTPのメソッドは以下の8つです。
|メソッド|用途|
|---|---|
|GET|リソースの取得|
|POST|リソースの送信|
|HEAD|ヘッダーの取得|
|PUT|リソースの更新|
|DELETE|リソースの削除を行います|
|CONNECT|双方向の通信を行う|
|OPTIONS|通信オプションを取得|
|TRACE|ループバックテストを実行|
|PATCH|リソースの１部分を変更|
:::

また、パスパラメータを使いたい場合は下のように書きます。
```yml
/tasks/{taskId}:
    get:
      operationId: todo-detail
      description: "todoタスクの詳細を表示します"
      tags:
        - todo
      parameters:
        - name: "taskId"
          in: path
          description: "詳細を取得したいtodoタスクのID"
          required: true
          type: "integer"
          format: "int64"
          example: 1
```

クエリパラメーターの場合は`in: path`を`query`に変えるだけです.
```yml
in: query
```

### RequestBody
RequestBodyでは、クライアントが送るリクエストの構造体を定義します。

jsonを送る場合は下のように定義します。

```yml
requestBody:
  content:
    application/json:
      schema:
        type: object
        properties:
          プロパティ1:
            type: string
            format: string
            description: "プロパティ1の説明"
            example: "プロパティ1の例"
          プロパティ2:
            type: string
            format: string
            description: "プロパティ2の説明"
            example: "プロパティ2の例"
        required:
          - プロパティ1
          - プロパティ2
```

`schema`の下にjsonの構造を書くことができます。

typeで以下のように設定できます。
|type|説明|
|---|---|
|object|Jsonオブジェクトを定義|
|array|Jsonオブジェクトの配列を定義|

:::note

#### `type array`の場合の書き方

`type array`の場合は下のようにitemsを追記します。
```yml
type: array
items:
  type: object
  properties:
```
:::

`properties`の下にJsonの要素となるプロパティを設定することができ、下の表のようにフィールドを設定できます。

|フィールド|記述内容|
|---|---|
|type|string, number, integer, boolean, array, fileからタイプを選ぶ|
|format|アプリで使用するフォーマットを指定する|
|description|説明を記述|
|example|実際に扱う際の具体値を設定|

`required`で必要なプロパティを指定できます。

### ResponseBody

ResponseBodyは返ってくる可能性のあるレスポンスを定義しています。

```yml
responses:
  '200':
    description: Successful response
    content:
      application/json:
        schema:
```

`responses`の下にはステータスコードを設定できます。

`description`でレスポンスに関する説明を書くことができます。

### components

先ほど定義したJsonのスキーマですがコンポーネント化して使い回すことが可能です。

```yml
components:
  schemas:
    object名:
      type: object
      properties:
        プロパティ1:
          type: string
          format: string
          description: "プロパティ1の説明"
          example: "プロパティ1の例"
        プロパティ2:
          type: string
          format: string
          description: "プロパティ2の説明"
          example: "プロパティ2の例"
      required:
        - プロパティ1
        - プロパティ2
```

このJsonオブジェクトコンポーネントはRequestBody等で書いていた内容を以下に変更すれば、簡単に適応できます。

```yml
content:
  application/json:
    schema:
      $ref: '#/components/schemas/object名'
```

## 実際に仕様書を書いてみる

今回は、ユーザー管理のアプリケーションを例にしてやってみようと思います。

実際に書いた仕様書は下のようになります。

```yml:example.swagger.yml
openapi: 3.0.0
info:
  title: User Management API
  description: A simple API to manage user information
  version: 1.0.0

tags:
  - name: users
    description: User Management API

paths:
  /users:
    get:
      summary: List all users
      tags:
        - users
      responses:
        '200':
          description: Successful response
          content:
            application/json:    
              schema:
                type: object
                properties:
                  users:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
    post:
      summary: Create a new user
      tags:
        - users
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewUser'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
  /users/{userId}:
    get:
      summary: Get a user by ID
      tags:
        - users
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
    put:
      summary: Update a user
      tags:
        - users
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewUser'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
    delete:
      summary: Delete a user
      tags:
        - users
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '204':
          description: Successful response

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        username:
          type: string
        email:
          type: string
        createdAt:
          type: string
          format: date-time
    NewUser:
      type: object
      properties:
        username:
          type: string
        email:
          type: string
      required:
        - username
        - email
```

大体このような感じで書けばいいので、ぜひ共同開発等で書いてみてください