これはなに
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の定義も同様に定義できる
- JSDocに
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の記述は人間がやるべき仕事ではないと思う。。