YeomanでフロントエンドとREST APIサーバーを同時に開発する方法

先月のHTML5など勉強会で、Yeoman超入門を発表したときに、Yeomanはフロントエンド開発専用にlocalhostサーバー立ち上げるからサーバーサイドとの同時開発はちょっと工夫がいるよね〜みたいな話題があって、参加されてたnode.jsに詳しい方からhttp-proxyつかってapiの部分リダイレクトかけたらいいよみたいな方法を教えてもらった。

なるほどそれは便利だなと思って実際書いてみたら手軽に使える感じにできたので書いておきます。ちなみに今週水曜日にGoogle Developers Liveに出演してYeomanのことを喋らせていただく機会に恵まれたので、その時の参照にも使えるかと思って。(ライブのスライドはこちら

 

Yeomanは $ grunt server で開発用のWebサーバーを起動することができる。デフォルトで http://localhost:9000/ 以降ファイルをセーブするたびに自動的に画面をリロードしてくれたりしてデバッグやDesigning in the Browserな点ですごく便利。

フロントエンドのみを開発する場合はこれで十分なんだけど、JSONベースのREST APIサーバーも同時に開発している際はちょっと問題がある。フロントエンド開発用サーバーがlocalhost:9000を専有しているからREST APIサーバーを同じオリジンで立てることができない。

つまり別のオリジン例えば localhost:3000 とかで立てないといけないんだけど、そうすると同一生成元ポリシーの関係でフロントエンド側からJSONを取得したりすることができなくなる。フロントエンドとAPIサーバーを同時進行で開発というのはよくあるし、End-to-Endテストしたい場合どうすんの?とか、ちょっと問題になってくるわけです。

こういうケースの場合、上記2つのサーバーとは別にプロキシサーバーを立てて、フロントエンドからAPIサーバーへのリクエストのみを別オリジンのAPIサーバーに渡してしまおうというのが今回のお話です。まあ上記の理屈がよくわからなくても実際やってみるとなんとなく感覚がわかると思います。

以下はYeomanが使える環境が整っている前提。

まず準備から。APIサーバーをいちいち書くのがめんどくさいので今回はnode.jsのeasymockモジュールを使います。REST APIサーバーのモックアップが超簡単に立てられるやつ。これをグローバルインストールします。

$ npm install -g easymock

次にyomanでフロントエンドのスケルトンを作成します。
仮に名前を test としましょう。

$ mkdir /YOUR PROJECT DIR/test
$ cd /YOUR PROJECT DIR/test
$ yo webapp
Out of the box I include HTML5 Boilerplate, jQuery and Modernizr.
Would you like to include Twitter Bootstrap for Sass? (Y/n) y
Would you like to include RequireJS (for AMD support)? (Y/n) y

プロキシーサーバー用のnode.jsモジュール http-proxy をインストールします。

$ npm install http-proxy

次にこのディレクトリにサーバー用のディレクトリを作成します。

$ mkdir -p server/api-server/api/items

easymock用のコンフィグファイルを各ディレクトリに配置します。
まず、以下のコードを server/api-server/api/config.json という名前で配置します。
server/api-server/api/config.json

次に以下のコードを server/api-server/api/items/_get.json という名前で配置します。

ここまでできたら、server/api-serverディレクトリに移動してAPIサーバーを起動してみましょう。

$ cd server/api-server
$ easymock
Server running on http://localhost:3000
Server running on http://localhost:3000/_documentation/

サーバーが起動するのでWebブラウザで http://localhost:3000/api/items/ にアクセスしてみます。

[
  {
    "id": 1,
    "user": "john",
    "message": "hello"
  },
  {
    "id": 2,
    "user": "bob",
    "message": "Hi!"
  },
  {
    "id": 3,
    "user": "mike",
    "message": "good bye."
  }
]    

というJSONが返ってくれば正常に起動しています。違う場合はディレクトリの配置、ファイル名などが違っている場合があるので確認しましょう。

このAPIサーバーはそのまま起動しておいて、別のターミナルウィンドウを開き、プロジェクトのディレクトリに移動します。

$ cd /YOUR PROJECT DIR/test

app/index.htmlのdiv.containerブロックを以下のように変更します。

次に app/scripts/app.coffee をapp.coffeeという名前で配置します。

フロントエンド開発用のサーバーを起ち上げます。

$ grunt server

Webブラウザが自動的に開いて以下の様な画面が表示されます。

f:id:bathtimefish:20130422120903p:plain

Webブラウザとwebappのサーバーをそのままにして、また別のターミナルウィンドウを開いてプロジェクト中のserverディレクトリに移動します。

 $ cd /YOUR PROJECT DIR/test/server

このserverディレクトリに proxyServer.coffee という名前で以下のコードを配置します。
proxyServer.coffee

proxyServer.coffeeを起動します。

$coffee proxyServer.coffee
Starting Server at http://localhost:8000/

localhost:8000 でプロキシサーバーが起動しました。
この状態で、Webブラウザで開いているwebapp画面のURLを http://localhost:8000/ に変更してみましょう。以下のような表示になるはずです。

f:id:bathtimefish:20130422122005p:plain

最初にwebappを起動した時とちがって、リストが表示されました。これは http://localhost:8000/api/items から取得したデータです。データの取得は app.coffeeの jQuery.getJSON で行なっています。

proxyServer.coffeeで、/api のリクエストのみを http://localhost:3000 へ再リクエストしています。これによってsame originリクエストとなりajaxリクエストに対して正常にJSONが返ってくることになりました。

ちなみに、app.coffee で $.getJSON('http://localhost:3000/api/items/', ... とやってポート3000のAPIサーバーに直接リクエストを送ってみると、、、

f:id:bathtimefish:20130422124134p:plain

というふうにcross originは許可されてないよと言われてデータが正常に取得できません。この場合はChrome先生がおっしゃってるようにAPIサーバでAccess-Control-Allow-OriginをHTTPヘッダに追加してレスポンスしてやる必要がありますが、開発するアプリケーションのフロントエンドとAPIサーバーが同一オリジンならばそもそもこの設定は不要です。

下手にやると脆弱性につながる危険性もあるので、上記のようにプロキシをかませる方法で開発を進めたほうがより良いんじゃないかなと思います。これだと設定も何も変えなくてもフロントエンドとAPIサーバーを本番環境にアップできるのでいいですね。

そうそう、このセットアップ以降はYeomanでのフロントエンド開発は localhost:8000 で行う。grunt で起動したポートが違うけどファイルのセーブとか監視してくれんの?と思うけど大丈夫。しっかり監視してリロードしてくれます。

ただし一度落として $ grunt server で再起動した場合は localhost:9000 で上がってくるのでもう一度手動で localhost:8000 に変更してやる必要があります。お忘れなく。

追記 2013.4.23
http://bit.ly/13r1xj5
このプロキシ使う方法を教えていただいた @kamiyam さんがもっと便利な方法を公開してくださいました。途中までやり方はいっしょだけど要のproxyの管理がgrunt taskになっていてよりスマートになってます。こちらのほうがオススメです!