2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JavaScript + ExpressでOpenAPISpecificationを書く

Posted at

これはなに

NodeJS + Expressのサーバー(JavaScript)でOpenAPI Specificationのドキュメントを用意したときのメモです。

  • プレーンなOASをyamlで書くのは避けたい
  • 非TypeScriptにてドキュメント自動生成も難しい

という状況だったので、JSDocのように実装内のコメントからOASドキュメントが生成できる仕組みを選択しました。

リソース

  • NodeJS
  • Express
  • swagger-jsdoc
    • コントローラ部分にアノテーション付きでJSDoc書くとOPenAPISpecificationを生成してくれるツール
  • ReDoc
    • OpenAPISpecificationのViewer。SwaggerUIよりも見やすいかも

準備

Expressの部分は割愛。swagger-jsdocは以下でインストール

npm install swagger-jsdoc

実装

oas.js

  • やっていること
    • /redoc アクセス時にReDocページが描画できるようにする
    • コマンド実行時にoas.jsonにOASドキュメントを出力する
async function lazySetup() {
  // swagger-jsdoc内部のpackage.jsonで "type": "module" 指定があり、requireできないため
  // await import で動的読み込みしている
  const swaggerJsdoc = await import('swagger-jsdoc');
  // infoやserverなど、OASの全体にかかる定義はここに定義する
  const swaggerDefinition = {
    openapi: '3.0.0',
    info: {
      title: 'hello API',
      version: '1.0.0',
      description: `
# ハローAPI
挨拶を返します
## subtitle
APIの説明をmarkdown形式で記述
`,
    },
    serve: [],
  };
  const options = {
    swaggerDefinition,
    apis: [
      // ここにpath定義を書いているファイルを列挙
      'server.js',
      // 'src/controllers/**/*.js', // ワイルドカードもOK
    ],
  };
  return await swaggerJsdoc.default(options);
}

// ReDoc描画のためのベースHTML
function generateRedocPage(spec) {
  return `
  <!DOCTYPE html>
  <html>
    <head>
      <title>ReDoc</title>
      <!-- needed for adaptive design -->
      <meta charset="utf-8"/>
      <link rel="icon" href="data:;base64,=">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
      <style>
        body {
          margin: 0;
          padding: 0;
        }
      </style>
    </head>
    <body>
      <div id="redoc-container"></div>
      <script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
      <script>
      const doc = '${JSON.stringify(spec)}'
      // JSON.parseのparseを通すため改行コードを変換
      const replaced = JSON.parse(doc.replace(/\\n/g, "<br>"))
      // ReDocは<br>があると上手くmarkdownパースしてくれないようなので、オブジェクト上で改行コードに変換。。
      replaced.info.description = replaced.info.description.replace(/<br>/g, "\\n")
      Redoc.init(replaced, {}, document.getElementById('redoc-container'))
      </script>
    </body>
  </html>
  `;
}

// /redoc にOASドキュメント描画のパスを追加
async function withOpenApiUi(app) {
  const spec = await lazySetup();
  const html = generateRedocPage(spec);
  app.get('/redoc', (req, res) => {
    return res.send(html);
  });
  return app;
}

module.exports = {
  withOpenApiUi
};


// コマンド実行時に、OASファイル生成するため
if (require.main === module) {
  const fs = require('fs');
  const fileName = 'oas.json';
  lazySetup().then(spec => {
    fs.writeFileSync(
      fileName,
      JSON.stringify(spec, null, '\t')
    );
    console.log(
      `\n🎉 bundled successfully in: ${fileName}`
      );
  });
}

server.js

  • contoroller部分にJSDocを記述
    • JSDocに@openapiというアノテーションつけてOASのpath定義を記述
    • path定義のほか、tagやcomponentの定義も同様に定義できる
const express = require('express');
const { withOpenApiUi } = require('./oas');
const app = express();

/**
 * @openapi
 * tags:
 *  - name: Hello
 *    description: 挨拶
 */


/**
 * @openapi
 * /hello:
 *  get:
 *    summary: hiと返す
 *    tags: [Hello]
 *    responses:
 *      '200':
 *        content:
 *          text/plain:
 *            schema:
 *              type: string
 *            example:
 *              hi
 */
app.get('/hello', (req, res) => {
  res.send('hi')
})

/**
 * @openapi
 * /hello/{name}:
 *  get:
 *    summary: 丁寧めにhiと返す
 *    tags: [Hello]
 *    parameters:
 *      - name: name
 *        in: path
 *        description: 名前
 *        required: true
 *        schema:
 *          type: string
 *    responses:
 *      '200':
 *        content:
 *          application/json:
 *            schema:
 *              type: "object"
 *              properties:
 *                message:
 *                  type: string
 *                to:
 *                  type: string
 *            example:
 *              message: こんにちは
 *              to: yamada
 */
app.get('/hello/:name', (req, res) => {
  res.send(`hi, Dear ${req.params.name}`)
})

withOpenApiUi(app).then((app) => {
  app.listen(8080, () => {
    console.log(`🚀 start`)
  })
})

実行

Expressサーバー実行時

node server.js

localhost:8080/redoc にアクセスすると、ReDocが閲覧できる

OASドキュメント出力

node oas.js

oas.jsonというファイルにOASの定義ファイルが出力される

まとめ

Open API Specificationの記述は人間がやるべき仕事ではないと思う。。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?