Azure API Apps を Node.js で作って Logic Apps で使う

今週リリースされた Azure API AppsASP.NET で開発するチュートリアルはあったのですが、 Node.js で開発するチュートリアルがありません。しかもドキュメントもほとんど無いです。

API Apps は Web Apps (Websites) 基盤上で提供されているようで、 Node.js も動かせるポテンシャルがあります。これは、なんとしてでも動かしてみなければなりません。

とりあえず動かしてみることが目的ですので、単純に現在時刻を提供するAPIを作ってみましょう。

1. 開発環境

準備ができたら、 Visual Studio を起動して

  • ファイル → 新規作成 → プロジェクト

すると新しいプロジェクトウィンドウが開くので、左のツリーから

  • テンプレート → JavaScript → Node.js

を選び、 Blank Azure Node.js Web Application を選択します。

名前は適当に "TimeApiApp" とでもしておきます。

OKで作成すると次のような状態になりますね。

2. 必要なモジュール

API Apps 環境で動かすには、Swagger 2.0 APIドキュメント仕様に対応させる必要があります。モジュールを使いましょう。

ここを右クリックして、 Install New npm Packages... を選択。

このような調子で、次のモジュールをインストールします。

3. Web.config の修正

デフォルトの内容だと、古い Node が動いてしまい morgan モジュールが正常に動作しません。次の内容に置き換えます。

<?xml version="1.0" encoding="utf-8"?>  
<configuration>  
  <system.webServer>
    <handlers>
      <add name="iisnode" path="server.js" verb="*" modules="iisnode" />
    </handlers>
    <iisnode nodeProcessCommandLine="D:\Program Files (x86)\nodejs\0.12.0\node.exe" />
    <rewrite>
      <rules>
        <rule name="app">
          <action type="Rewrite" url="server.js" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>  

4. 必要なファイルの追加

ここを右クリックして、 追加 → 新しい項目の追加

次の名前ファイルを追加します。

  • apiapp.json
  • apidef.json

新しいフォルダー。

  • handlers

次は新しく作った handlers フォルダー上で右クリックして、次の名前のファイルを追加。

  • time.js

まとめると、次のようになります。

5. apiapp.json

さて、 apiapp.json を定義します。3月末現在のところ、この内容はAzureプラットフォームから無視されるようですが、いずれ利用されると思います。次の内容をコピペします。

{
  "$schema": "http://json-schema.org/schemas/2014-11-01/apiapp.json#",
  "id": "Time",
  "namespace": "",
  "gateway": "2015-01-14",
  "links": [],
  "endpoints": {
    "apiDefinition": "/swagger/docs/v1",
    "status": null
  }
}

書式についてはこちら: http://azure.microsoft.com/en-us/documentation/articles/App-Service-dotnet-create-api-app-visual-studio/#review-apiappjson

一部の項目は後述の server.js で動的に生成するため省略します。

※ endpoints.apiDefinition のパスは変更しないでください。 前述の通り apiapp.json の定義が無視されていますので今これを変更すると正常に動かなくなります。

6. apidef.json

これは Swagger 2.0 仕様に準拠した API定義ドキュメントファイルです。

まずはこれを作成し、これに従ってAPIを実装します。

{
  "swagger": "2.0",
  "info": {
    "version": "1.0.0",
    "title": "Time",
    "description": "This is Time provider.",
    "contact": {
      "name": "kanreisa",
      "url": "https://github.com/kanreisa"
    },
    "license": {
      "name": "MIT",
      "url": "https://github.com/kanreisa/node-azure-time-api-app"
    }
  },
  "schemes": [
    "http"
  ],
  "paths": {
    "/time": {
      "get": {
        "summary": "Time",
        "description": "Just returns current time.",
        "operationId": "Time",
        "consumes": [ ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "name": "dummy",
            "in": "query",
            "x-ms-summary": "This just returns current time.",
            "required": false,
            "type": "string",
            "default": "this is dummy input."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "$ref": "#/definitions/Time"
            }
          }
        },
        "deprecated": false
      }
    }
  },
  "definitions": {
    "Time": {
      "required": [
        "time"
      ],
      "type": "object",
      "properties": {
        "time": {
          "type": "string",
          "x-ms-summary": "Unixtime (ms)"
        }
      }
    }
  }
}

これでよろしいのではないでしょうか。

7. server.js

これがサーバー本体です。

'use strict';

var express = require('express');  
var morgan = require('morgan');  
var swaggerize = require('swaggerize-express');

var apiapp = require('./apiapp.json');  
var apidef = require('./apidef.json');

// complete apidef.json
if (process.env.WEBSITE_HOSTNAME) {  
    apidef.host = process.env.WEBSITE_HOSTNAME;
}

// map apidef.json to apiapp.json
apiapp.version = apidef.info.version;  
apiapp.author = apidef.info.contact.name;  
apiapp.title = apidef.info.title;  
apiapp.summary = apidef.info.description;

apiapp.license = {  
    type: apidef.info.license.name,
    url: apidef.info.license.url
};

apiapp.links.push({  
    text: apidef.info.contact.name,
    url: apidef.info.contact.url
});

var app = express();

app.use(morgan('combined'));

app.use(swaggerize({  
    api: apidef,
    docspath: apiapp.endpoints.apiDefinition,
    handlers: './handlers'
}));

app.get('/apiapp.json', function (req, res) {

    res.json(apiapp);
});

app.get('/', function (req, res) {

    res.setHeader('Content-Type', 'text/plain; charset=utf-8');
    res.end('Hello World.');
});

app.listen(process.env.PORT || 1337);  

色がついてなくてごめんなさい。コピペしてご確認ください。

8. handlers/time.js

Time API の本体がこれです。 apidef.json で定義した通りに実装します。

'use strict';

module.exports = {  
    get: function (req, res) {
        res.json({
            time: Date.now().toString(10)
        });
    }
};

これだけですが。

9. ローカルで動かしてみる

Ctrl + F5 キーを押します。

このように Hello World. と表示されます。アドレスに /time を追加すると、先ほど作った Time API の実行結果が得られると思います。

ついでに次のようなURLにも正常にアクセスできるか確認します。

10. 発行

ここを右クリックして、 発行 です。

Microsoft Azure API Apps (Preview) を選択

新規.. を選択

内容を確認して OK を押します。

OK

待ちます。

Azure API App Time Api App provisioned と表示されたら、また先ほどの手順で発行画面を表示します。

発行 ボタンを押しましょう。

しばらくすると...

やった!Azure API Apps 環境で Node が動きましたね!

11. 動作確認

ちゃんと動作しているか Azure Portal でも確認してみましょう。

動いてます!ついに(現時点で)公式ドキュメントにないクエストをやり遂げました。

12. Logic Apps で使ってみる

1時間ごとに現在のUnixtimeをつぶやく Logic App を作ってみます。

今回作成した API App は何もパラメーターを要求しないので、dummy input を用意しています。(現状おそらくこれはAzure側のバグですが、parametersが存在しないか空の場合、Portalでエラーを吐きます(ブラウザコンソールを見ないと分からない)。Previewなので大目にみましょう)

Tweet Text は @concat('Current Unixtime: ', body('timeapiapp').time) とします。

すると・・・

やりました!ツイートできました!

・・・日本語でのツイートの仕方はまだ分かりません。 疲れたので今日はここまで

ソースコードはこちら

https://github.com/kanreisa/node-azure-time-api-app