SwaggerでRESTful環境を構築する

本投稿は、以下のような思いを持った方に適しています。

* RESTful環境を作りたいが、準備が面倒だ。
* RESTful環境を作っても、時間がたつと呼び出し方を忘れてしまう。
* 最近聞く、Swaggerって何？

また、本投稿では、以下の知識を有していることが前提となっています。

* Node.jsを触ったことがある

それでは、RESTfulのAPI仕様の定義・構築・開発・メンテナンスまで、便利な環境であるSwaggerを使って、かっこいいRESTful環境を構築します。
以下の順番で進んでいきます。

* swagger-nodeのインストール
* swaggerプロジェクトの作成
* API仕様ドキュメントの環境構築
* サンプルAPI(Google Homeをしゃべらせる)の作成

# swagger-nodeのインストール

※node.jsのバージョンは、await/asyncを使うかもしれないと思ったので、v8.11.3で行きました。

> nvm install 8.11.3
> nvm alias default 8.11.3

それでは早速、NPMを使ってswagger-nodeをインストールします。
swagger-nodeは、Node.jsでSwaggerを使った開発を簡単便利にするための環境です。
（これがなかったら、Swaggerを使うことはなかったでしょう）

> npm install swagger -g

[参考情報]
https://github.com/swagger-api/swagger-node

[参考情報]
よく使うコマンドを列挙しておきます。

* swagger project create [プロジェクト名]
 * Swaggerプロジェクトを作成します。

* swagger project start
 * Swaggerプロジェクトを実行し、RESTful環境を立ち上げます。

* swagger project edit -s
 * Swagger定義ファイルを編集するためのWebページを立ち上げます。


# Swaggerプロジェクトの作成

適当なフォルダで、以下を実行すると、RESTful環境のひな型が生成されます。

> swagger project create [プロジェクト名]

プロジェクト名は、今回は適当に、「restful_test」とします。
途中に、Frameworkを何にするか聞かれますが、定番の「express」を選択します。
実行が完了すると、カレントディレクトリの下に、restful_testというフォルダが作られ、その中にRESTful環境のひな型のファイル群が生成されています。

以降は、[プロジェクト名]のフォルダで作業します。
> cd [プロジェクト名]

すでにひな型としてHelloWorldのAPIが実装されているので、さっそくSwagger環境を実行して、RESTful環境を立ち上げましょう。

```
> swagger project start
Starting: /XXXX/restful_test/app.js...
  project started here: http://localhost:10010/
  project will restart on changes.
  to restart at any time, enter `rs`
try this:
curl http://127.0.0.1:10010/hello?name=Scott
```

コンソールに出力されている通りのURLをブラウザから開いてみます。
ブラウザは、Chromeがおすすめです。

http://127.0.0.1:10010/hello?name=Scott

もし、Raspberry Piなどを使っていて、Swagger環境と異なるPCからブラウザ表示する場合は、127.0.0.1のところを、実際のSwagger環境のドメイン名を指定してください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/547f4251-ec99-6560-4dc2-45df2083952c.png)

 > “Hello､ Scott!”

と表示されました。QueryStringに指定した名前が含まれています。

ポート番号10010は、デフォルトでapp.jsに定義されていますので、変更することが可能です。
また、以下のようにしても変更できます。

> env PORT=XXXX swagger project start

それでは、実際の仕組みを順に追っていきます。

URLを見てみると、/helloというAPIを呼び出しています。
この定義は、Swagger定義ファイルにあります。ファイルは以下の場所にあります。

./api/swagger/swagger.yaml

該当箇所は、以下のあたりです。

```swagger.yaml
paths:
  /hello:
    # binds a127 app logic to a route
    x-swagger-router-controller: hello_world
    get:
      description: Returns 'Hello' to the caller
      # used as the method name of the controller
      operationId: hello
      parameters:
        - name: name
          in: query
          description: The name of the person to whom to say hello
          required: false
          type: string
```

pathsの下の階層で、/hello と記述されており、これがAPIのエンドポイントに相当します。
次の、get: が、エンドポイントで受け付けるメソッドになります。ほかにも、post、deleteなども指定できます。
parameters配下は入力パラメータ定義なのですが、queryとしてnameという名前でstringを指定すると定義されています。
さきほど、以下のようにブラウザのURLに指定しましたが、この定義に従っていたわけです。

/hello?name=Scott

実装の方を見ていきます。
エンドポイントと、Node.jsの実装とのマッピングは、同じSwagger定義ファイルで定義しています。Swaggerの標準的な仕様ではありませんが、swagger-nodeで独自の定義です。

x-swagger-router-controller: hello_world

の部分が、対象のエンドポイントのメソッドを処理する実装のコントローラ(のファイル名)になります。上記の場合、hello_world.jsというファイル名が対応付けられています。
また、1つのファイル名に、複数のエンドポイントのメソッドを定義できるため、同じコントローラのファイルの中でもどの関数定義を使うのかどうかを以下で指定しています。

operationId: hello

それでは、hello_world.jsを見ていきます。
ファイルは、./api/controllers/hello_world.jsにあります。

注目すべきは以下の部分です。

```hello_world.js
module.exports = {
  hello: hello
};

function hello(req, res) {
・・・
}
```

公開するオブジェクト(module.exports)に、「operationId : 関数名」 というプロパティを複数定義できます。
上記の場合ですと、operationId:helloに、関数helloを割り当てています。
したがって、この関数helloの中で、引数nameを受け取り、Hello・・・の文字列を返すように実装しているのがわかります。

あとは、これと同じようにすれば、独自のエンドポイントを定義、実装ができます。要するに、

* Swagger.yamlにエンドポイントとメソッドを定義し、さらにコントローラ名とoperationIdを定義
* コントローラ名のnode.jsファイルを作成し、operationIdと関数のマッピングを定義
* 関数を実装

Swagger定義ファイルは、各有志のサイトで書き方を教えてくれていますので、いろいろなサイトを見て回るのが良いかと思います。

# API仕様ドキュメントの環境構築

Swagger定義ファイルは、テキストファイルですので、エディタで開いてみてもよいのですが、なかなか初めての場合はわかりにくいです。
そこで、HTMLのページとして整形して表示してくれるツールを構築してみます。

* Swagger-Editor
 * Swagger定義ファイルを構文チェックしながら編集したり、API仕様を可視化したり、REST呼び出しができます。
 * また、REST呼び出しするためのクライアント側ライブラリもSwagger定義ファイルの内容に従って生成できます。

* Swagger-UI
 * Swagger-Editorとほぼ同じですが、編集機能やライブラリ生成機能はないです。

これらのツールを使って整形表示するのですが、まずは、CORS対応させる必要があります。Swagger定義ファイルを提供しているサイトと、上記のSwagger-Editor/UIを提供するサイトが異なるためです。

> npm install cors --save

その後、app.jsを編集します。

```
・・・
module.exports = app; // for testing

var cors = require('cors'); //★この行を追記
app.use(cors()); //★この行を追記
・・・
```

もう一度、Swagger環境を立ち上げておきます。

> swagger project start

ブラウザから以下のURLを表示させます。

http://localhost:10010/swagger

そうすると、JSON形式のSwagger定義ファイルが取得できます。
これを、Swagger-Editor/UIで表示させてみます。

Swagger-Editor/UIのセットアップは単純で、アーカイブファイルをダウンロードしてから、適当な場所に解凍し、ブラウザからindex.htmlを参照するだけです。

* Swagger-Editor
 * https://github.com/swagger-api/swagger-editor
 * 解凍したフォルダの直下に、index.htmlがあります。

* Swagger-UI
 * https://github.com/swagger-api/swagger-ui
 * 解凍したフォルダの下に、dist フォルダがあり、その中にindex.htmlがあります。

index.htmlを参照したときには、サンプルのSwagger定義ファイルが表示されており、自身で表示したいSwagger定義ファイルのURLを手で指定する必要があります。
そこで、ページ参照と同時に、自動的に指定したSwagger定義ファイルのURLを参照するようにします。
以下は、Swagger-Editor/UIをWebサーバに配置して参照した場合のURLです。

http://localhost/swagger-editor/?url=http://localhost:10010/swagger
http://localhost/swagger-ui/?url=http://localhost:10010/swagger

[Swagger-Editor]
![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/d42fbac2-4caf-33b4-80c6-9bd794ac9b0a.png)

[Swagger-UI]
![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/5010fc6b-a065-0e99-8b77-746eac5bda1e.png)

# サンプルAPIの作成

最後に、APIを１つ追加してみたいと思います。

今回追加するのは、RESTfulのAPIにメッセージ付きで呼び出すと、Google Homeからそのメッセージを喋らせる、というものです。
Google HomeはChromecastに対応していて、その機能を流用します。

まずは、Swagger定義ファイルで、RESTful APIの仕様を定義します。

* エンドポイント名は「/voice」とします。
* メソッドは、「POST」とします。
* しゃべらせるメッセージをmessageという名前で文字列を渡します。そのとき、渡しやすいようにJSON形式で渡します。
* ついでに、greeting:trueとすることで、時刻に合わせて挨拶を入れるようにします。
* レスポンスもJSON形式とし、要求された文字messageと、実際にしゃべった言葉utteranceを返します。

それがこちら。Swagger定義ファイルからの抜粋です。以下を追記してください。

```swagger.yaml
paths:
  /voice:
    x-swagger-router-controller: voice
    post:
      description: Post voice to google home
      operationId: voice_post
      parameters:
        - in: body
          name: body
          required: true
          schema:
            $ref: '#/definitions/VoiceRequest'
      responses:
        '200':
          description: Success
          schema:
            $ref: '#/definitions/VoiceResponse'
        default:
          description: Error
          schema:
            $ref: "#/definitions/ErrorResponse"

definitions:
  VoiceRequest:
    type: object
    required:
      - message
    properties:
      message:
        type: string
      greeting:
        type: boolean
  VoiceResponse:
    type: object
    properties:
      message:
        type: string
      utterance:
        type: string
```

それでは、実装に入ります。

Google Homeにしゃべらせるために、外部ライブラリを使います。

> npm install castv2-client --save
> npm install google-tts-api --save

Google Homeにしゃべらせるために、対象のGoogle HomeのIPアドレスを調べておきます。
IPアドレスは、AndroidのHomeアプリから、デバイスを選択して、デバイス設定にある情報 に書いてあります。

```voice.js
'use strict';
var util = require('util');
var Client = require('castv2-client').Client;
var MediaReceiver = require('castv2-client').DefaultMediaReceiver;
var googletts = require('google-tts-api');

var deviceAddress = 'XXX.XXX.XXX.XXX'; //★ここにGoogle HomeのIPアドレスを記載

module.exports = {
    voice_post: voice
};

function voice(req, res){
    console.log(req.body);

    var message = req.body.message || '言葉を識別できませんでした。';

    var greeting = '';
    if( req.body.greeting !== undefined && req.body.greeting ){
        var hour = new Date().getHours();
        if( hour >= 18 || hour < 5 )
            greeting = 'こんばんは。';
        else if( hour >= 5 && hour < 12 )
            greeting = 'おはようございます。';
        else
            greeting = 'こんにちは。';
    }

    var hello = greeting + message;
    var _res = res;
	homeSpeech(hello, deviceAddress)
	.then( function(result) {
        _res.json({ message: message, utterance: hello });
	})
	.catch( function(err){
        _res.json({ err: err });
	});
}

function homeSpeech(text, host){
	return googletts(text, 'ja-JP', 1)
	.then(function(url){
		return playUrl(url, host);
	});
}

function playUrl(url, host){
	return new Promise((resolve, reject) =>{
		var client = new Client();
		client.connect(host, function(){
			client.launch(MediaReceiver, function(err, player){
				var media = {
					contentId: url,
					contentType: 'audio/mp3',
					streamType: 'BUFFERED'
				};
			
				player.load(media, { autoplay: true }, function(err, status){
					client.close();
					resolve('Device notified');
				});
			});
		});
		
		client.on('error', function(err){
			console.log('Error: %s', err.message);
			client.close();
			reject(err);
		});
	})
}
```

早速、実行してみましょう。

> swagger project start

※Swagger定義ファイルがFixしたら、以下でもよいです。
> node app.js

もし、ポート番号やホスト名を変えていたら、swagger定義ファイルの以下の部分も変更してからSwagger環境を立ち上げます。

host: localhost:10010

ブラウザから、もう一度以下にアクセスしてください。

http://localhost/swagger-ui/?url=http://localhost:10010/swagger

/voiceというPOSTのエンドポイントが増えています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/92a96020-11ef-70bb-e790-1f9c607d4c75.png)

「Try it out」ボタンを押下して、実行してみます。
「Edit Value」にしゃべりたい言葉を入力し、最後に「Execute」ボタンを押下します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/d43b2757-c171-ba02-6d0c-6cd29f7791a2.png)

Google Homeがしゃべった後、RESTful APIからのレスポンスも表示されました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/0adc5c02-b652-b7ad-6cf0-d9e2202b22bf.png)

#おまけ

最後までご参照いただきありがとうございます。
ぜひ以下も参考にしてください。きっと役に立つと信じています。

[Swagger定義ファイルにSecurityDefinitionsを定義する](https://qiita.com/poruruba/items/a384b34408fbba3b5e47)
　Authorizationヘッダを使っている方はこの投稿をもぜひ！
[SwaggerでLambdaのデバッグ環境を作る(1)](https://qiita.com/poruruba/items/e3a46787c79517a815f9)
　NodeのSwagger環境をさらに便利にしています。

以上です。