19
2016

swagger-jsdocでAPIドキュメントを書こう

CATEGORYJavaScript
Node.jsでAPIを作るにあたって、APIのドキュメントを書く良い方法がないかと探してた結果、Swagger という奴がいいらしいと聞いて試してみたら思った以上に良さげだったのでその使い方とかまとめ。

まず Swagger について解説。調べたり使ってみた感じから、特徴やらを列挙してみる。
  • APIドキュメントはいろんなツールがあるが、その中では割と知名度が高いらしい。
  • YAMLとかJSONとかソースコメントとかいろんな書き方ができる。
  • 各言語用のツールが用意されている。※APIドキュメント自体は言語非依存
  • ドキュメントにAPIを実行するためのフォームが付いてくる。
  • 共通化を図るOpen API仕様というのが Swagger ベースになるらしいので、将来性が期待できそう。
  • いろんなツールがあって、導入が複雑。
はい、利点の方は申し分ない。自分が過去やった仕事だと、wikiとphpdocとテスト用フォームが三重管理になってしまい遂にはwiki以外全部更新されなくなって削除、という惨事もあったので、一つで賄えるというのは非常にありがたい。

…が JSDoc みたいにコマンドを一つインストールすればいいわけではなく、設定やらややこしい面もあったので、以下JavaScriptでの個人的ベストプラクティスを解説する。

インストール

今回インストールしたツールは以下の2つ。いろいろあってややこしいけど、とりあえずこれだけあればJSDoc+αは出来た。
  • swagger-jsdoc - ソースコメント等でAPIを書くためのライブラリ
  • Swagger-UI - APIドキュメントを表示するWebビューアー
前者はnpmでインストール、後者はReleaseのページにあるzipなりを落としてきて、その中のdistディレクトリを適当にnginxなりNode.jsなりの静的ファイルが見れる場所に置けばOK。
自分の場合 Swagger-UI も同じサーバーに置いたけど、生成されたファイルを開くための単なるビューアーなので、違うサーバーてもよさそう。
でも、クロスドメインのアクセス制限とか引っかかったりするので、トラブル避けるなら同じサーバーが無難か。
(試すだけならデモサイトもあり。)

日本語でJavaScriptでSwaggerでググると swagger-express使う解説が出てくるけど、swagger-express はAPI仕様が古いバージョン1.4までしか対応していないので注意!最新の2.0に対応している swagger-jsdoc を使おう。構文が違うから調べてて混乱する(した)。

swagger-jsdocの組み込み

swagger-jsdoc はWebアプリに組み込む感じで動作するので、app.js やらに組み込む。こんな感じ。
if (app.get('env') === 'development') {
const swaggerJSDoc = require('swagger-jsdoc');
const swaggerSpec = swaggerJSDoc({
swaggerDefinition: {
swagger: "2.0",
info: {
title: "xxxアプリAPI",
version: "1.0.0",
description: "APIの説明とかを記入。",
},
basePath: "/api",
consumes: ["application/json"],
produces: ["application/json", "text/plain"],
},
apis: ["./api/users.js", "./api/books.js"],
});

app.get('/api-docs.json', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.send(swaggerSpec);
});
}
前段が swagger-jsdoc の初期化で、後段はWebアクセスでJSONを出力させる場合のパスの設定。swagger-jsdoc 自体はCLIでも使えるみたいだが、今回はこのパスで生成したAPIドキュメントのJSONを、Swagger-UIでWeb経由で表示させる形で進める。

swaggerDefinition には、たぶんSwaggerのドキュメントがそのまま何でも入れられる。でも swagger-jsdoc 使うなら、出来るだけコメントで書きたいと思うので、実際に使うときは外部ファイルに切り出した上で、アプリの説明とか複数ソースに跨る共通のdefinitions定義とかだけこっちで入れてる。

apis には、コメントを読み込むソースのファイル名を指定する。
こっちも実際は具体的なファイル名を指定するんじゃなくて、プログラム上でcontrollersディレクトリのファイル名全部とって渡すとか、そういう使い方すると思われ。

Swaggerコメントの書き方

まず最初に、Swaggerのコメントはタグ自体は @swagger だけで、実際のコメントは以降のサンプルのように @swagger の次行以降にYAMLで記述する。
一般的なJSDocのコメントとは混ぜられない模様で、普通のコメントがあるコメントブロックとは別に分けて書く必要がある。
以下、各構文の書き方を解説する。なおswagger構文のリファレンスはここ。でもいまいち分かり辛い。

API(基本)

基本的なAPIの書き方はこんな感じ。@swagger と、tags じゃなくて tag が使えることを除けば普通の構文と同様だと思われる(tag も普通の構文にあったりする?)。
/**
* @swagger
* tag:
* name: users
* description: ユーザー関連API
*/
/**
* @swagger
* /users/login:
* post:
* tags:
* - users
* summary: ユーザー認証
* description: ユーザー名とパスワードで認証を行う。
* parameters:
* - in: body
* name: body
* description: ユーザー情報
* required: true
* schema:
* type: object
* required:
* - name
* - password
* properties:
* name:
* type: string
* description: ユーザー名
* password:
* type: string
* format: password
* description: パスワード
* responses:
* 200:
* description: 認証OK
* schema:
* type: object
* properties:
* token:
* type: string
* description: 認証トークン
* 400:
* description: 認証NG
* schema:
* type: string
*/
app.post('/users/login', function(req, res, next) {
/* 省略 */
});
このサンプルはExpressなので、パラメータはbodyにJSONで入ってくる想定で、戻り値もJSON(エラーの時はメッセージのみ)。
JSONの場合は schema の下に type: object ~みたいにオブジェクトの形を定義する。

細部はともかく、だいたい初見でもなんとなく意味は理解できると思われる。こんな感じに書いていく。

オブジェクト

上だけでも気合入れまくれば出来ないことはないけど、毎回全部書いてたらシャレにならない。
Swaggerには要素を共通化する構文があるのでそれを紹介。まずはオブジェクトから。
/**
* @swagger
* definition:
* LoginUser:
* type: object
* description: ユーザー情報
* required:
* - name
* - password
* properties:
* name:
* type: string
* description: ユーザー名
* password:
* type: string
* format: password
* description: パスワード
*/
/**
* @swagger
* /users/login:
* post:
* tags:
* - users
* summary: ユーザー認証
* description: ユーザー名とパスワードで認証を行う。
* parameters:
* - in: body
* name: body
* description: ユーザー情報
* required: true
* schema:
* $ref: '#/definitions/LoginUser'
* responses:
* 200:
* description: 認証OK
* schema:
* type: object
* properties:
* token:
* type: string
* description: 認証トークン
* 400:
* description: 認証NG
* schema:
* type: string
*/
app.post('/users/login', function(req, res, next) {
/* 省略 */
});
definition を用いるとオブジェクトの定義を外出しできる。
参照は schema の下などで $ref: '#/definitions/LoginUser' のようにやればOK。
サンプルは後述だが、定義されたオブジェクトにパラメータを追加したり、組み込んだ新しいオブジェクトを定義することもできるので、オブジェクト系はこれでガッツリ共通化が可能。

パラメータ

次にパラメータの定義。下はRESTなAPIでパスにIDが入っているイメージ。
/**
* @swagger
* parameter:
* userIdPathParam:
* in: path
* name: id
* description: ユーザーID
* required: true
* type: integer
* format: int32
*/
/**
* @swagger
* /users/{id}:
* get:
* tags:
* - users
* summary: ユーザー情報取得
* description: 指定されたユーザーの情報を取得する。
* parameters:
* - $ref: '#/parameters/userIdPathParam'
* responses:
* 200:
* description: 取得成功
* schema:
* allOf:
* - $ref: '#/definitions/LoginUser'
* - properties:
* id:
* type: integer
* description: ユーザーID
* 404:
* description: 該当データ無し
* schema:
* type: string
*/
app.get('/users/:id', function(req, res, next) {
/* 省略 */
});
parameter を用いるとAPIのパラメータ定義部分の記述を外出しできる。
名前が違うだけで、切り出し方や参照方法はオブジェクトと同じ。元の奴と同じ書式でそのまま外に出せばいい。

あと、ここでは前述の手順で外出ししたオブジェクトに、idというプロパティを追加して流用している。
allOfを使うと、複数の定義の内容が一つにマージされるみたい。

レスポンス

次はレスポンス。これも異常系なんかは延々同じフォーマットになるので共通化したいところで、以下のように行ける。
/**
* @swagger
* response:
* NotFound:
* description: 該当データ無し
* schema:
* type: string
*/
/**
* @swagger
* /users/{id}:
* get:
* tags:
* - users
* summary: ユーザー情報取得
* description: 指定されたユーザーの情報を取得する。
* parameters:
* - $ref: '#/parameters/userIdPathParam'
* responses:
* 200:
* description: 取得成功
* schema:
* allOf:
* - $ref: '#/definitions/LoginUser'
* - properties:
* id:
* type: integer
* description: ユーザーID
* 404:
* $ref: '#/responses/NotFound'
*/
app.get('/users/:id', function(req, res, next) {
/* 省略 */
});
ここまでくると、APIのコメントに残ったのがAPI固有の記述だけになってきてだいぶスッキリする。

認証

次はちょっとジャンルが変わって認証の構文。SwaggerではBASIC認証と、ヘッダーやクエリーで認証情報を渡すAPI認証、それにOAuth2がサポートされているらしい。
以下は、Authorizationヘッダーで認証トークンを渡してOKの場合のみ処理できる、パスワード変更をイメージしたサンプル。
securityDefinitions: {
AuthToken: {
type: "apiKey",
in: "header",
name: "Authorization",
description: "認証トークン",
}
}
まず認証パラメータの定義。securityDefinitions はコメントではなく最初に設定した swaggerDefinition の中に定義する。
認証はアプリで共通だろうから、コメントでも書けるかもしれないけどそっちで良いと思われる。
/**
* @swagger
* /users/me/password:
* put:
* tags:
* - users
* summary: パスワード変更
* description: 認証中ユーザーのパスワードを変更する。
* security:
* - AuthToken: []
* parameters:
* - in: body
* name: body
* description: ユーザー情報
* required: true
* schema:
* type: object
* properties:
* password:
* type: string
* format: password
* description: パスワード
* responses:
* 200:
* description: 更新成功
* schema:
* type: string
* 401:
* $ref: '#/responses/Unauthorized'
*/
app.put('/users/me/password', passport.authenticate('jwt', { session: false }), function(req, res, next) {
/* 省略 */
});
security が認証が必要なAPIだよって宣言。種別が apiKey の場合、名前だけで値は空配列を指定する。
ヘッダーとか通常のパラメータとしても指定できるけど、こうやってちゃんと定義すると、Swagger-UIでは認証パラメータを入れる特別な欄が使えるようになる。

API(応用例)

こうやっていろんなものを組み合わせたAPIがこんな感じ。
/**
* @swagger
* /users/{id}:
* put:
* tags:
* - users
* summary: ユーザー情報更新
* description: 指定されたユーザーの情報を更新する。
* security:
* - AuthToken: []
* parameters:
* - $ref: '#/parameters/userIdPathParam'
* - in: body
* name: body
* description: ユーザー情報。ユーザー名とパスワードが変更可能
* required: true
* schema:
* $ref: '#/definitions/LoginUser'
* responses:
* 200:
* description: 更新成功
* schema:
* $ref: '#/definitions/User'
* 400:
* $ref: '#/responses/BadRequest'
* 401:
* $ref: '#/responses/Unauthorized'
* 403:
* $ref: '#/responses/Forbidden'
*/
app.put('/users/:id', passport.authenticate('jwt', { session: false }), function(req, res, next) {
/* 省略 */
});
オブジェクトとかは、実際は一度しか使わない奴はべたに書いちゃうのでこんな綺麗にはならないけど、やろうと思えばここまで行ける。
まあこの陰には、たくさんのオブジェクトやらの定義が他にあるわけなんだけど…。

Swagger-UIでの表示

はい。ではここまで長々と構文を覚え、苦労してコメントを書いたので、次はお待ちかねのドキュメントを表示する版です!
…で、まずここまで書いたコメント、どうやってコンパイルというかなんというかされるかというと、CLIを用いる方法と、APIを用いる方法があって、今回は最初にも書いた通り後者で行く。
具体的には、一番最初に設定した /api-docs.json を叩くと、コンパイルされたドキュメントが出力されます。JSONで!
(なお、構文エラーがあるとここでJSONがおかしかったり、そもそもWebアプリがエラーで起動しなかったりします。怖!)

出来たJSONがこれ。このJSONがSwaggerの標準的なファイル?らしく、いろんなサービスとかにインポートできたりするっぽいのだが、生憎とJSONで出てきても人間様は困ってしまうので、これをSwagger-UIに食わせる。
手順は簡単、Swagger-UIの上部のURL欄に、このJSONのURLを入れて「Explore」ボタンで読み込むだけ。
http://localhost/swagger/?url=/api-docs.json みたいにクエリーでも指定できるので、これブックマークしとくと便利。)

Swagger-UI画面サンプル

するとこんな感じで念願のAPIドキュメント兼フォームが表示される。万歳。
後は大体画面から見て感覚で使えるかと。しいて言うなら、認証は右上の「Authorize」ボタンか赤いビックリマークで出来る。
後、右下にJSONのバリデーションチェックらしいエラーが出たりするものの、これはJSONのAPIにアクセス制限かかってるとバリデーションができないのが原因っぽいので、あんまり害はないはず。


ということで、一度やり方が分かってしまえば後はコメントを積み重ねるだけかと。
快適なドキュメント作成ライフをお過ごしください!
スポンサーサイト

Tag: JavaScript

0 Comments

Leave a comment