手動のAPIドキュメント作成はやめよう:SwaggerとExpressで自動化する

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

手動ドキュメント作成の大きなコスト

バックエンド開発者としての1年目、私は毎週約20%の時間をREADMEファイルやPostmanコレクションの更新に費やしていました。それは負け戦のようなものでした。チームメイトが私に知らせずにフィールドをstringからintegerに変更した瞬間、ドキュメントは嘘になってしまいます。その結果、連携が壊れ、「ねえ、なんで/usersエンドポイントが400エラーを返してくるの?」といった恐ろしいSlackメッセージが届くことになるのです。

手動のドキュメント作成は生産性を低下させます。人間の記憶に頼ることになりますが、プロジェクトが複雑になるにつれて記憶は必ず曖昧になります。OpenAPI(旧称Swagger)は、REST APIのための形式的でマシン読み取り可能な仕様を提供することで、この問題を解決します。これにより、開発者はソースコードに触れることなく、サービスの機能を理解できるようになります。

Swagger UIを統合することで、その仕様をインタラクティブなプレイグラウンド(遊び場)に変えることができます。私の経験では、ドキュメントを自動化することで、開発者のオンボーディング時間を50%近く短縮でき、「ドキュメントの陳腐化」問題を完全に解消できました。ドキュメントが常にコードの生きた姿を反映するようになるのです。

インストール:ツールの準備

手順を進めるには、標準的なNode.js環境が必要です。コードとUIの橋渡しをするために、以下の2つのライブラリを使用します:

  • swagger-jsdoc:JSDocコメントを解析し、JSON形式のOpenAPI仕様に変換します。
  • swagger-ui-express:Expressアプリケーション内でSwagger UIダッシュボードを直接ホストします。

新しいプロジェクトを作成し、これらの依存関係をインストールしましょう:

mkdir express-swagger-demo
cd express-swagger-demo
npm init -y
npm install express swagger-ui-express swagger-jsdoc

また、nodemonの追加もおすすめします。ドキュメント用のコメントを更新した際にサーバーを自動的に再起動してくれるため、プロジェクト期間中に発生する何百回もの手動再起動の手間を省けます:

npm install --save-dev nodemon

設定:SwaggerとExpressの連携

このセットアップの本当の利点は、コードとの「近さ」にあります。ドキュメントが、それが記述するロジックのすぐ隣に存在することになります。まずはserver.jsを作成し、基本的なExpressのボイラープレートをセットアップしましょう。

1. 基本的なボイラープレート

const express = require('express');
const swaggerJsDoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');

const app = express();
app.use(express.json());

const PORT = process.env.PORT || 3000;

2. Swaggerオプションの定義

設定オブジェクトはAPIの「身分証明書」のような役割を果たします。バージョン、タイトル、そしてswagger-jsdocがコメントをスキャンする特定のファイルを定義します。

const swaggerOptions = {
  swaggerDefinition: {
    openapi: '3.0.0',
    info: {
      title: '顧客管理API',
      version: '1.0.0',
      description: 'Swaggerで生成されたExpress APIドキュメント',
      contact: {
        name: 'バックエンドチーム',
        url: 'https://itfromzero.com'
      },
      servers: [{ url: 'http://localhost:3000' }]
    },
  },
  // API定義が含まれるファイルを指定します
  apis: ['server.js', './routes/*.js'], 
};

const swaggerDocs = swaggerJsDoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs));

3. JSDocによるルートのドキュメント化

ここからが実践的な作業です。別のYAMLファイルを管理する代わりに、ルートハンドラーの直前で@openapiタグを使用します。これにより、ドキュメントとコードを完全に同期させることができます。

/**
 * @openapi
 * /customers:
 *   get:
 *     summary: 顧客リストの取得
 *     responses:
 *       200:
 *         description: 顧客オブジェクトのJSON配列
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 type: object
 *                 properties:
 *                   id: { type: integer }
 *                   name: { type: string }
 */
app.get('/customers', (req, res) => {
  res.status(200).send([
    { id: 1, name: '田中 太郎' },
    { id: 2, name: '佐藤 花子' }
  ]);
});

/**
 * @openapi
 * /customers:
 *   post:
 *     summary: 新規顧客の作成
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               name: { type: string }
 *     responses:
 *       201:
 *         description: 顧客が正常に作成されました
 */
app.post('/customers', (req, res) => {
  const { name } = req.body;
  res.status(201).send({ message: `顧客 ${name} が作成されました` });
});

app.listen(PORT, () => {
  console.log(`サーバーが http://localhost:${PORT} で起動しました`);
  console.log(`ドキュメントは http://localhost:${PORT}/api-docs で確認できます`);
});

@openapiタグを使用することで、コード自体がドキュメントになります。新しいエンジニアがチームに加わったとき、彼らはルートのロジックとAPIの仕様を同時に読み解くことができます。

検証:インタラクティブなUIのテスト

node server.jsでサーバーを実行し、http://localhost:3000/api-docsを開いてください。ドキュメント化したすべてのエンドポイントがリスト化された、プロフェッショナルなインターフェースが表示されるはずです。

「Try It Out」機能

Swagger UIの最も強力な部分は「Try it out」ボタンです。ブラウザを離れることなく、パラメータを入力してサーバーに実際のリクエストを送信できます。これにより、共有が漏れたり古くなったりしがちなPostmanコレクションを共有する必要が事実上なくなります。

再利用可能なスキーマによるスケーリング

APIが20、50と増えていくと、server.jsは煩雑になります。整理された状態を保つために、再利用可能なコンポーネントを定義しましょう。これはDRY(Don’t Repeat Yourself:同じことを繰り返さない)原則に従っており、更新もはるかに速くなります。

/**
 * @openapi
 * components:
 *   schemas:
 *     Customer:
 *       type: object
 *       required: [name]
 *       properties:
 *         id:
 *           type: integer
 *           description: 自動生成されたID
 *         name:
 *           type: string
 *           description: 顧客のフルネーム
 */

$refを使用して、任意のルートでこのスキーマを参照できます。もし顧客オブジェクトにphone_numberフィールドを追加する必要が出てきても、1箇所変更するだけで済みます。

// ルート定義内
responses:
  200:
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Customer'

本番環境での安全性とベストプラクティス

Swagger UIは開発には不可欠ですが、本番環境での公開には注意が必要です。公開APIであれば素晴らしい機能ですが、内部マイクロサービスの場合は、認証の背後に隠すか、環境に応じて無効化することをお勧めします。

if (process.env.NODE_ENV !== 'production') {
  app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs));
}

ドキュメントの自動化は、それを「面倒な作業」から「開発サイクルの一部」へと変えてくれます。私がプルリクエスト(PR)をレビューするときは、JSDocの更新をコードそのものと同じくらい重要視しています。ロジックが変わったのにドキュメントが変わっていない場合、そのPRはマージしません。この規律こそが、ドキュメントがチーム全体にとっての唯一の真実(Source of Truth)であり続けることを保証するのです。

Share: