Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

NestJS でサーバを起動せずに OpenAPI の仕様書を取得する

はじめに

この記事は モバイルファクトリー Advent Calendar 2019 の18日目の記事です。
こんにちは、ブロックチェーンチームのエンジニアの id:odan3240 です。

NestJS では @nestjs/swagger を用いることで、コントローラーの定義から OpenAPI (swagger) の仕様書を生成することができます。 このモジュールの使い方などは、先日の NestJS Advent Calendar 2019 に記事を投稿しましたので、そちらをご覧ください。

qiita.com

上のリンクの記事では、JSON 形式の OpenAPI の仕様書を取得する方法として http://localhost:3000/api-json にアクセスする方法を紹介しています。しかし、実際にアプリケーションを開発していく上でこの方法では CI のワークフローに組み込みにくいなどの問題があります。
この記事ではこの問題を解決する方法を紹介します。

問題の詳細

http://localhost:3000/api-json にアクセスする方法には、「バックエンドのサーバは起動している」という前提条件が必要になります。
この前提条件はかなり強い条件です。例えば MySQL などのデータストアを利用しているバックエンドのサーバを考えます。このサーバの起動時にデータストアにコネクションを張る設定をしていると、データストアの存在が前提条件に追加されます。つまり、バックエンドのサーバの他にデータストアも起動しておかないと http://localhost:3000/api-json にアクセスできず仕様書を取得することができません。
CI で OpenAPI の仕様書を出力してクライアントのコードを自動生成を行うワークフローを組んでいる場合、CI にデータストアを用意する必要があるため、手間がかかります。

解決方法

@nestjs/swagger は Controller の実装の定義から OpenAPI の仕様書を生成するモジュールですので、原理的にはサーバの起動なしに Controller のソースコードから仕様書を生成できるはずです。この記事で紹介する解決方法は、NestJS の DI 機構を用いて、これを実現します。

@nestjs/testing には Test.createTestingModule という関数があります。これを使うとクラスのモックを簡単に差し込むことができます。

Testing | NestJS - A progressive Node.js web framework

具体的なコードは サンプルリポジトリsrc/openapi/generate.ts にあります。

ポイントは次の通りです。

  • Reflect.getMetadata を使って AppModule の Controller と Provider を取得する
    • これらを createTestingModule に引数に渡す
      • Provider は useValue でモックする
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { Test } from '@nestjs/testing';
import { AppModule } from '../app.module';

async function bootstrap(): Promise<void> {
  const controllers = Reflect.getMetadata('controllers', AppModule);
  const providers = Reflect.getMetadata('providers', AppModule);
  const mockedProviders = providers.map(provider => {
    return {
      provide: provider.name,
      useValue: {},
    };
  });

  const testingModule = await Test.createTestingModule({
    controllers: controllers,
    providers: mockedProviders,
  }).compile();

  const app = await testingModule.createNestApplication();

  const options = new DocumentBuilder()
    .setTitle('アドベントカレンダーサンプル')
    .build();
  const document = SwaggerModule.createDocument(app, options);
  console.error(JSON.stringify(document, null, 2));
}
bootstrap();

このファイルを ts-node で実行すると JSON 形式の OpenAPI の仕様書を取得することができます。

$ yarn ts-node src/openapi/generate.ts > /dev/null
{
  "openapi": "3.0.0",
  "info": {
    "title": "アドベントカレンダーサンプル",
    "description": "",
    "version": "1.0.0",
    "contact": {}
  },
  "tags": [],
  "servers": [],
  "components": {},
  "paths": {
    "/": {
      "get": {
        "operationId": "getHello",
        "responses": {
          "200": {
            "description": ""
          }
        }
      }
    }
  }
}

終わりに

NestJS の DI 機構を用いて Controller が依存するクラスをモックすることで、バックエンドのサーバを起動せずに OpenAPI の仕様書を取得する方法を紹介しました。これにより、データストアなどの他のミドルウェアに対する依存がなくなったため、CI のワークフローに組み込みやすくなりました。