【Swagger】出来るだけ楽してAPI仕様書を作りたい


# はじめに

皆さん API 仕様書を書くのは好きですか！
私はあまり好きではありません！！！
<br/>

理由は色々ありますが、大きいところで言うと以下です。

1. 記述量が多くシンプルに時間が掛かる
2. OpenAPI の記法が独特で書くのがめんどくさい
3. API に変更があった際更新漏れが発生しがち
   <br/>

つまり何が言いたいかというと **出来るだけ楽したい！！！**
<br/>

ということで今回のゴールとしては

**「.js ファイルに記述した内容が CLI コマンドの実行で Swagger で見れるようになる」**

ことを目指します。
<br/>

この”Swagger で見れるようにする”については、具体的に以下の 2 つの手法を紹介していきます。

- VSCode の拡張機能を使った表示
- ブラウザでの表示

なお、API 仕様書といえば Excel や Notion で作成するものも指しますが、今回の記事では OpenAPI で書く API 仕様書に限って話していきたいと思います。

# 2 つの開発手法

実装に入る前に API の開発手法について簡単に紹介しておきます。

API を実装する際の開発手法には主に 2 つのアプローチがあります。

- **デザインファースト（トップダウン型）**: OpenAPI からコードを生成する手法
- **コードファースト（ボトムアップ型）**: コードから OpenAPI の仕様を生成する
  <br/>

それぞれの適用ケースについては以下の記事が分かりやすかったので、気になる方は見てみてください。

https://one-sthead.hatenablog.com/entry/2021/02/21/225015

<br/>
今回は「楽に API 仕様書を作る」ことが目的のため、コードファースト（ボトムアップ型）の手法を紹介していきます。

# 技術スタック

- Node.js: 18.20.4
- Express: 4.21.1
- Sequelize: 6.37.5
- swagger-jsdoc: 6.2.8
- swagger-ui-express: 4.6.3

# **ディレクトリ構成**

今回の記事に関連のある部分だけですが、ディレクトリ構成を紹介しておきます。

```terminal
app
├── swagger
│   ├── swagger.config.js
│   └── schemas
│       ├── Pet.js
│       └── Tag.js
├── docs
│   └── openapi.yml
├── router.js
└── app.js
```

# .js ファイルに OpenAPI 仕様を記述する

まずはいずれの表示方法でも必要になる、.js ファイルへの OpenAPI 仕様の記述について書いていきたいと思います。

## swagger-jsdoc のインストール

今回使うライブラリは swagger-jsdoc です。

https://www.npmjs.com/package/swagger-jsdoc

有名なライブラリなので名前は聞いたことがある方は多いと思います。
<br/>

このライブラリは JSDoc のアノテーションをされたソースコードを読み込み、OpenAPI 仕様を生成してくれます。

以下のコマンドでインストールを行いましょう。

```terminal
$ npm install swagger-jsdoc --save
```

## Schema の作成

インストールが完了したら早速.js ファイルに OpenAPI で出力する内容を書いていきます。

Schema については API のファイルに直接書き込んでもいいのですが、大規模開発だと長くなりがちですし、汎用性が低いため今回は別ファイルに書いていきます。

（あとは JSDoc を使っておいてなんですが、コメントでの記入はシンプルに書きにくい。）
<br/>

今回は Swagger 公式のサンプルを参考に Pet と Tag の Schema を作っていきます。

```js:Tag.js
module.exports = {
  type: "object",
  properties: {
    id: { type: "integer", format: "int64" },
    name: { type: "string" },
  },
};
```

```js:Pet.js
module.exports = {
  type: "object",
  properties: {
    id: { type: "integer", format: "int64", example: 10 },
    name: { type: "string", example: "doggie" },
    category: {
      type: "object",
      properties: {
        id: { type: "integer", format: "int64", example: 1 },
        name: { type: "string", example: "Dogs" },
      },
    },
    photoUrls: { type: "array", items: { type: "string" } },
    tags: {
      type: "array",
      items: { $ref: "#/components/schemas/Tag" },
    },
  },
};
```

## API に仕様を記載

Schema ができたら router.js に API を記述していきます。

ここは`@openapi`を挿入する以外は、OpenAPI と同じ構文で書けば問題ないです。

```js:router.js
/**
 * @openapi
 * /pet/{petId}:
 *   get:
 *     tags:
 *       - pet
 *     summary: Find pet by ID
 *     description: Returns a single pet
 *     parameters:
 *       - name: petId
 *         in: path
 *         description: ID of pet to return
 *         required: true
 *         schema:
 *           type: integer
 *           format: int64
 *     responses:
 *       200:
 *         description: successful operation
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Pet'
 */
app.get("/pet/:petId", function (req, res, next) {
  /* 省略 */
});
```

## OpenAPI 仕様書の定義ファイルを作成

定義ファイルは以下の通り書いていきます。

app.js に直接記述する人も多いようですが、私は今回別で swagger.config.js というファイルを作成し module.exports する形を取っています。

```js:swagger.config.js
const swaggerJSDoc = require('swagger-jsdoc');
const path = require("path");
const schemasDir = path.resolve(__dirname, "./schemas");

const options = {
  definition: {
    openapi: "3.0.0",
    info: {
      title: "Swagger Petstore - OpenAPI 3.0",
      version: "1.0.0",
      description: "APIの説明を記入",
    },
    servers: [
      {
        url: "https://petstore3.swagger.io/api/v3",
        description: "Swagger公式のサンプルのURL",
      },
    ],
    tags: [
      {
        name: 'pet',
        description: 'Everything about your Pets',
      },
    ],
    components: {
      schemas: {
        Tag: require(path.join(schemasDir, "Tag")),
        Pet: require(path.join(schemasDir, "Pet")),
      },
    },
  },
  apis: ["./router.js"], // API 定義のコメントがあるルーターを指定
};

const swaggerSpec = swaggerJSDoc(options);

module.exports = swaggerSpec;
```

先ほど作った Schema はここで読み込んでいきます。

:::note warn
**apis に.ts ファイルを渡すと、エンドポイントが表示されません。**

[解決策](https://github.com/Surnet/swagger-jsdoc/issues/150#issuecomment-450869488)もあるようですが、全ての.ts ファイルをスキャンすることになりロードに時間が掛るようです。
.ts ファイルを使いたい場合は、build 後の.js ファイルを渡すなどの工夫が必要そうです。
:::

# VSCode の拡張機能で Swagger を表示する

「ブラウザでプレビューできればいいので.yml ファイルは必要ないよ！」という方は、次の ”ブラウザで Swagger を表示する” の章にスキップして頂いて構いません。

## CLI コマンドで OpenAPI 仕様の.yml ファイルを生成する

今回は docs 配下に`openapi.yml`というファイルで出力を行います。

コマンドを以下の通りじっこうして`Swagger specification is ready.`が表示されれば、出力が問題なく出来ています。

```terminal
$ # npx swagger-jsdoc -d <OpenAPI定義ファイル> <API定義ファイル> -o <出力ファイル>
$ npx swagger-jsdoc -d app/swagger/swagger.config.js app/router.js -o docs/openapi.yml
Swagger specification is ready.
```

<br/>
docs 配下に出来ている`openapi.yml`を開くと出力された内容が確認できると思います。

```yml:openapi.yml
openapi: 3.0.0
info:
  title: Swagger Petstore - OpenAPI 3.0
  version: 1.0.0
  description: APIの説明を記入
servers:
  - url: https://petstore3.swagger.io/api/v3
    description: 上記はSwaggerのサンプルのurlです。適宜変更してください。
tags:
  - name: pet
    description: Everything about your Pets
components:
  schemas:
    Tag:
      type: object
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
    Pet:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        name:
          type: string
          example: doggie
        category:
          type: object
          properties:
            id:
              type: integer
              format: int64
              example: 1
            name:
              type: string
              example: Dogs
        photoUrls:
          type: array
          items:
            type: string
        tags:
          type: array
          items:
            $ref: "#/components/schemas/Tag"
paths:
  "/pet/{petId}":
    get:
      tags:
        - pet
      summary: Find pet by ID
      description: Returns a single pet
      parameters:
        - name: petId
          in: path
          description: ID of pet to return
          required: true
          schema:
            type: integer
            format: int64
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
```

## VSCode の拡張機能で表示する

では、出力された.yml ファイルを VSCode の拡張機能を使って表示してみましょう。

今回使う拡張機能は**OpenAPI (Swagger) Editor**です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/56ac187c-5dca-2079-5266-c6ebb932c137.png)
<br/>

Swagger Viewer という拡張機能もありますが、こちらはコピペが出来ず不便なので、こちらの拡張機能をオススメします。

インストールを行った後は.yml ファイルを開いた状態で、画像のアイコンをクリックすることでプレビュー画面を表示することが出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/8a8e58bd-26a0-ec4f-b184-91bc40e64851.png)
<br/>

以下のようにプレビューが表示されれば完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/cb402e3e-8f59-0086-2878-580c84c5793e.png)

# ブラウザで Swagger を表示する

次はブラウザでの表示を実装していきましょう。
こちらも上記の章同様、不要な方はスキップして頂いて構いません。

## swagger-ui-express のインストール

ライブラリは swagger-ui-express を使っていきます。

https://www.npmjs.com/package/swagger-ui-express

<br/>
以下のコマンドでインストールを行いましょう。

```terminal
$ npm install swagger-ui-express
```

:::note warn
**バージョンに注意してください。**

バージョンが新しすぎた場合、うまく動作しないケースがありました。
うまくいかない場合は本記事と同じ v4.6.3 で試してみてください。
:::

## セットアップ

インストールが完了したら、app.js で swagger-ui-express のセットアップを行っていきます。
セットアップで渡す定義ファイルは、初めに作成した swagger.config.js を使います。

app.js に以下の内容を追加しましょう。

```js:app.js
const express = require('express');
const cors = require('cors');

const swaggerUi = require('swagger-ui-express');
const swaggerSpec = require('./swagger/swagger.config');

const app = express();

app.use(cors());

// http://localhost:3000/api-docs/に表示
app.use('/api-docs', swaggerUi.serveWithOptions({ redirect: false }));
app.get('/api-docs', swaggerUi.setup(swaggerSpec));
```

今回私のプロジェクトでは API を`http://localhost:3000`に立てているため、上記の記載の場合、ブラウザでのプレビューはその配下の`/api-docs/`に表示されるようになります。
<br/>

:::note warn
**公式の README とは違う実装をしています。**

12 行目の部分は公式の README では`swaggerUi.serve`と記述されていますが、私の場合これではうまく表示が出来ませんでした。
そのため以下の記事を参考に`swaggerUi.serveWithOptions({ redirect: false })`を採用しています。
https://github.com/scottie1984/swagger-ui-express/issues/62
:::

## CLI コマンドでブラウザにプレビューを表示する

セットアップが完了したら、CLI では app.js を呼び出すだけです。

以下の通り node で呼び出しを行います。

```terminal
node app/app.js
```

実行が完了したら、設定した URL にアクセスしてみましょう。

http://localhost:3000/api-docs/

以下の画像の通り、プレビューが表示されれば完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/0ca43124-9c36-d267-7332-d084861525d0.png)
<br/>

:::note warn
**最後の'/'は必須です。**

`http://localhost:3000/api-docs`でアクセスすると以下の様なエラーが出て、表示ができないため注意しましょう。

```terminal
Refused to apply style from 'http://localhost:3000/swagger-ui.css' because its MIME type ('application/json') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
```

:::

# オマケ: Authentication の設定

本記事の内容は以上で終了にはなるのですが、Swagger で Authentication を利用している方は多いと思いましたので、オマケとして swagger-jsdoc を使っている際の書き方について簡単に触れておきたいと思います。
<br/>

Authentication の内容については、OpenAPI の定義ファイルに記載する必要があります。

例として今回の swagger.config.js に公式サイトに記載されている X-API-KEY の内容を追記してみるとこんな感じです。

```js:swagger.config.js
const swaggerJSDoc = require('swagger-jsdoc');
const path = require("path");
const schemasDir = path.resolve(__dirname, "./schemas");

const options = {
  definition: {
    openapi: "3.0.0",
    info: {
      title: "Swagger Petstore - OpenAPI 3.0",
      version: "1.0.0",
      description: "APIの説明を記入",
    },
    servers: [
      {
        url: "https://petstore3.swagger.io/api/v3",
        description: "Swagger公式のサンプルのURL",
      },
    ],
    tags: [
      {
        name: 'pet',
        description: 'Everything about your Pets',
      },
    ],
    components: {
      // ----- この範囲を追加 -----
      securityDefinitions: {
        APIKeyHeader: {
          type: 'apiKey'
          in: 'header'
          name: 'X-API-Key'
        },
      },
      // ------------------------
      schemas: {
        Tag: require(path.join(schemasDir, "Tag")),
        Pet: require(path.join(schemasDir, "Pet")),
      },
    },
  },
  apis: ["./router.js"],
};

const swaggerSpec = swaggerJSDoc(options);

module.exports = swaggerSpec;
```

<br/>
それぞれの API への記載は OpenAPI 仕様で同様に書けばいいので、以下のようになります。

```js:router.js
/**
 * @openapi
 * /pet/{petId}:
 *   get:
 *     tags:
 *       - pet
 *     summary: Find pet by ID
 *     description: Returns a single pet
 *     parameters:
 *       - name: petId
 *         in: path
 *         description: ID of pet to return
 *         required: true
 *         schema:
 *           type: integer
 *           format: int64
 *     responses:
 *       200:
 *         description: successful operation
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Pet'
 *     security:
 *       - APIKeyHeader: []
 */
app.get("/pet/:petId", function (req, res, next) {
  /* 省略 */
});
```

# 最後に

今回は swagger-jsdoc を使った OpenAPI 仕様書の出力と、swagger-ui-express を使ったブラウザ出力を書いてきました。

<br/>
なお、筆者の「出来るだけ楽して API 仕様書を作りたい」という欲求は継続しています。

具体的には router ファイルに書いているコメントについては、結局 OpenAPI 仕様で書いているので、正直書きにくさは感じています。

正直定義ファイルだけ書いて、あとはコードの内容をもとに自動生成してほしい...。

そのため、読んでくださった皆さんの方で「こうした方がもっと楽になるよ！」や「このライブラリがオススメだよ！」などありましたら、是非コメントなどで教えていただけるとありがたいです！
<br/>

この記事で皆さんの API 仕様書作りが少しでも楽になれば幸いです。