jQuery
http://jquery.com/

jQuery日本語リファレンス
http://semooh.jp/jquery/

7つのサンプルでjQueryを学ぼう!「jQueryが全く分からない人のため」の超初級者向け入門講座
http://ozpa-h4.com/2012/11/07/jquery-lecture/
テンプレート(Jade)
Expressフレームワークでのアプリケーション開発の基本に、ビューの使用がある。ビューとは、テンプレートコードを記述したファイルのこと。Expressは運用上分離すべき機能のコードを分離できるように設計されている。app.jsにはサーバスクリプトを記述し、それぞれのURLルートにリクエストを受けた場合の挙動はroutes/index.jsに記述し、クライアントへのアウトプットを生成するロジックはviewsディレクトリ内のコードに記述する。

テンプレート言語はロジックによる動的コンテンツ生成の基礎部分を提供し、テンプレートエンジンはそのロジックをHTMLに変換する。

ExpressのデフォルトテンプレートエンジンであるJadeを使ってデータを表示する。

(Expressでは、Jade以外のテンプレートエンジンを使用することもできる。Expressがサポートしているテンプレートエンジンはhttps://github.com/visionmedia/express/wikiにリストされている。テンプレートエンジン間の比較はhttp://paularmstrong.github.io/node-templates/(英語)で参照できる。)

mimoeappディレクトリにprofile.jsというファイルを用意する。

profile.js
module.exports = {
ryan: {
name: "Ryan Dahl",
irc: "ryah",
twitter: "ryah",
github: "ry",
location: "San Francisco, USA",
description: "Creator of node.js"
}, isaac: {
name: "Isaac Schlueter",
irc: "isaacs",
twitter: "izs",
github: "isaacs",
location: "San Francisco, USA",
description: "Author of npm, core contributor"
}, bert: {
name: "Bert Belder",
irc: "piscisaureus",
twitter: "piscisaureus",
github: "piscisaureus",
location: "Netherlands",
description: "Windows support, overall contributor"
}, tj: {
name: "TJ Holowaychuk",
irc: "tjholowaychuk",
twitter: "tjholowaychuk",
github: "visionmedia",
location: "Victoria, BC, Canada",
description: "Author of express, jade and other popular modules"
}, felix: {
name: "Felix Geisendorfer",
irc: "felixge",
twitter: "felixge",
github: "felixge",
location: "Berlin, Germany",
description: "Author of formidable, active core developer"
}
};

app.getなどでapp.jsに設定したルートが残っている場合は、トップディレクトリ(/)以外のすべてのルートを削除しておく。

環境設定内で、Jadeはすでにデフォルトテンプレートエンジンに設定されているため、app,jsに手を加える必要はない。

routes/index.jsでは、index以外へのすべてのルートを削除しておく。削除すると以下の状態になっているはず。

routes/index.js
exports.index = function(req, res){
  res.render('index', { title: 'Express' });
};

res.renderメソッドはviewsディレクトリに格納されているindex.jadeを呼び出し、profile.jsのオブジェクトデータのテンプレートとして使用する。

profiles変数にデータを読み込んで、そのオブジェクトをres.renderの2つ目のパラメータに渡すオブジェクトに含めておく。

routes/index.js
var profiles = require('../profiles.js');   //追加
exports.index = function(req, res){
  res.render('index', { title: 'Profiles',profiles: profiles });  //変更
};

indexページのタイトルをProfilesに変更した。

ここでviews/index.jadeを編集する。express(1)コマンドで生成されたindex.ladeには次のような記述が含まれている。

views/index.jade
  h1= title
  p Welcome to #{title}

ここにtable要素を加えて、profiles.jsの各メンバーの詳細情報を出力する。

views/index.jade
//以下を追加
//インデントに注意! table#profilesのインデントレベルはh1、pと同レベルにしなければならない(tabのインデントではエラーが出る)

  table#profiles
    tr
      th Name
      th Irc
      th Twitter
      th Github
      th Locatio
n
      th Description
        each profile, id in profiles
          tr(id=id)
            each val in profile
              td #{val}

記述を終えたら、アプリケーションを起動する。

ターミナル
$ node app.js

ブラウザでhttp://localhost:3000にアクセスすると、次のように表示される。



res.renderの最初のパラメータにファイル名(index)を渡すと、viewsディレクトリにある同名のテンプレートファイル(index.jade)を呼び出す。Expressは、以下に示すapp,jsの記述でviewsディレクトリ内にJadeファイルが格納されていることを認識しているため、.Jade拡張子は省略できる。

app.js
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

res.renderの2つ目のパラメータにはオブジェクトを渡す。このコードでは、titleとprofilesという2つのプロパティを持っている。これらのプロパティは、Jadeテンプレートでローカル変数として利用できる。ローカル変数としてバッファされた値を等号(=)で呼び出せる。あるいはJadeのインターポレーションラッピング(#{})を使って、変数名を#{val}のようにラッピングする。

Jadeは冗長な部分を削ぎ落としたテンプレート言語。ブラケット(<>)を取り去ったマークアップタグや、インデントベースの文法とブロック拡張記法(インデントの代わりにコロンでネスト構造を表現する方法)を持つ。文法もできるだけ小さいサイズに抑えていて、例えばHTMLタグのidとclassは、それぞれハッシュ記号(#)とドット(.)で表す。

例えば、

  table#profiles
    th Name

というJadeコードは、次のHTMLを生成する。

<table id="profiles"><th>Name</th></table>

(Jadeの文法の詳細についてはhttps://github.com/visionmedia/jadeを参照する。)

Jadeはイテレーションもサポートしている。このコードでは、2つのeachイテレータを使ってprofilesオブジェクトから値を抽出している。

views/index.jade
        each profile, id in profiles
          tr(id=id)
            each val in profile
              td #{val}

最初のeach文はprofilesオブジェクトを走査して(in profiles)、それぞれのプロフィール情報を持ったオブジェクトをprofileオブジェクトに格納し、さらにそれらのオブジェクトのID(ryan、issac、bertなど)をid変数に格納している。

次のtr文は、それぞれのプロフィール情報を<tr>タグに設定している。<tr>タグのid属性には、<tr id="ID値">のようにprofileのID(id変数)の値を設定します。Jadeでタグのの属性を設定する場合は、タグ名に続く括弧内にキーと値を挿入する(tr(key=value,Key2=value))。id属性を設定するショートカットとしてハッシュ記号(#)を使用できるが、id属性値として変数を渡す場合は括弧内に指定する方法で記述を行う。

2番目のeach文では、trタグの行から一段インデントが入っている。JavaScriptと違い、Jadeはインデントもロジックの一部なので、インデントの有無がコードの動作に直接影響を及ぼす。このeach文では、最初のeach文で得たprofileオブジェクトの内容を走査し、それぞれの値をval変数に格納している。次の行のtd文の#{val}でそれぞれの値を出力する。



JadeにJavaScriptを挿入する
Jadeにもスクリプト(JavaScript)を埋め込むことができる。スクリプトを使ってヘッダをより簡単に出力してみる。index.jadeを次のように変更する。

views/index.jade
extends layout

block content
  h1= title
  p Welcome to #{title}

  - var headers = ['Name','Irc','Twitter','Github','Location','Description'];

  table#profiles
    tr
      each header in headers
        th= header
      each profile, id in profiles
        tr(id=id)
          each val in profile
            td #{val}

Jadeは、行頭にダッシュ(-)があると、その行にJavaScriptが記述されていると認識する。ここではheaders配列を宣言し、Jadeのeachイテレータでヘッダを出力している。等号(=)を使ってheader変数を評価している。

JavaScriptではなく、Jadeの記法で同様の記述を行うこともできる。上記のコードの「- var headers...」の行を以下のように変更する。

views/index.jade
  headers = ['Name','Irc','Twitter','Github','Location','Description'];

Jade、はこの記述を先ほどの埋め込みJavaScriptの形に内部変換し実行する。



//Stylusがうまく起動せず、エラーを起こすので、一旦中断!!

Nodeクックブック p.186

/*
CSSプロセッサを使う(Stylus)
テンプレートエンジンでHTMLを出力できたので、今度はCSSでスタイルを適用する。プレーンなCSSを記述することもできるが、ExpressはCSSプリプロセッサと連携することができる。

StylusはExpressと連携できるCSSメタ言語の1つ。StylusはExpressとの連携を念頭に置いて開発されており、Jadeテンプレートエンジンと似通った方針で開発されている。

Stylusにスポットライトを当て、先ほど作成したprofilesテーブルにStylusを使ってスタイルを当てる方法を学ぶ。

mimoeappのディレクトリを利用する。

最初にStylusをセットアップする。
今回は、ここまでで構築している既存のプロジェクトを使用するが、最初から始める場合はexpress(1)コマンドでStylusベースのプロジェクトを生成する。

ターミナル
$ express --css stylus ★アプリケーション名

このコマンドを実行すると、package.jsonのdependenciesやapp.jsの環境設定の項目にstylusが追加される。app.jsには以下のように追加される。

app.use(require('stylus').middleware(path.join(__dirname, 'public')));

今回は、mimoeappプロジェクトディレクトリにあるpackage.jsonを編集する。

package.json
{  "name": "mimoeapp",  "version": "0.0.1",  "private": true,  "scripts": {    "start": "node app.js"  },  "dependencies": {    "express": "3.4.8",    "jade": "*",    "stylus": "*"  }}
依存関係を加えたところで、以下のコマンドを実行して、依存モジュールをインストールする。

ターミナル
$ npm install

app.jsの環境設定に、次の文を追加しておく。

app.js
app.set('view engine', 'jade');
//次のミドルウェアを追加
app.use(require('stylus').middleware({ src:__dirname + '/views', dest:__dirname + '/public'}));app.use(express.favicon());app.use(express.logger('dev'));app.use(express.json());
srcとdestにそれぞれ違うディレクトリを設定した。

Stylusファイルを格納するviews/stylesheetsディレクトリを作成しておく。ExpressはこのディレクトリからStylusファイルを自動的に見つけ出して処理を行い、CSSを生成する。生成したCSSはpublicディレクトリにある同じ名前のディレクトリ(public/stylesheets)に保存される。

Stylusファイルの編集を始める。まず、public/stylesheets/style.cssファイルをviews/stylesheetsディレクトリにコピーして、style.stylと拡張子を変更しておく。ファイルに以下のCSSが記述されている。

views/stylesheets/style.styl
body {  padding: 50px;  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;}
a {  color: #00B7FF;}

StylusはプレーンなCSSと完全互換なので、ファイルを編集する必要はない。しかしここでは、Stylus独自の記法を紹介しておく。現在のCSSの内容から波括弧({})やコロン(:)、セミコロン(;)を取り去って、Stylusのインデントベースフォーマットに変換する。

views/stylesheets/style.styl
body  padding 50px  font 14px "Lucida Grande", Helvetica, Arial, sans-serif
a  color #00B7FF
ここで、先ほど作成した#profileテーブルにスタイルを適用する。@extendディレクティブを使って、#profileテーブルとそれぞれのtdやthの上下左右にpaddingを与える。以下をstyle.stylに追記する。

views/stylesheets/style.styl
.pad  padding 0.5em#profiles  @extend .pad  th    @extend .pad  td    @extend .pad

ブラウザが新しいCSSプロパティをサポートする際、そのプロパティが標準として成熟していない場合にはベンダープレフィックス(ブラウザベンダー固有の接頭辞)を追加してサポートすることがある。例えば、角丸の半径を指定するborder-radiusプロパティは、Firefoxでは当初-moz-border-radiusとして実装され、WebKit系ブラウザでは-webkit-border-radiusとして実装された。

移り変わりの激しいCSSプロパティに追随してCSSを書き、メンテナンスを行うには大変な労力を必要とする。Stylusのmixin機能がこの労力を大幅に低減する。

views/stylesheets/style.styl
//以下を#profilesの前に追加
borderIt(rad = 0, size = 1px, type = solid, col = #000)  border size type col  if rad   -webkit-border-radius rad   -moz-border-radius rad   border-radius rad
このmixinを#profilesテーブル、th、tdの各要素に適用する。

views/stylesheets/style.styl
#profiles  borderIt 20px 2px  //追加  @extend .pad  th    @extend .pad  td    @extend .pad    borderIt(col: #000 + 80%)  //追加
app.jsを実行し、ブラウザからサーバにアクセスすると、public/stylesheets/style.cssが生成され、ブラウザには以下のようなページが出力される。



画像



このStylusはExpressのモジュールとして動作しているが、StylusはExpressを介することなく動作することができる。しかし、Expressのモジュールとして追加した場合、CSSの生成に便利なミドルウェアメソッドを使うことができる。

express(1)コマンドでStylusを含んだプロジェクトを生成すると、app.useでStylusを呼び出す際のオプションにはsrcプロパティだけが設定されている。Stylusは.styl拡張子を持ったファイルを取得し、CSSに変換して、その結果を格納した.cssファイルを同じディレクトリに配置する。destプロパティを設定すると、Stylusコードをsrcディレクトリから読み出し、destディレクトリにCSSを配置する。

srcディレクトリはviewsに、destディレクトリはpublicに設定している。しかし、styles.stylがviewsのサブディレクトリにあったとしても、Stylusはそれらのファイルを発見し、destディレクトリ配下に対応するサブディレクトリに配置する。

layout.jadeファイルには、(Webサイトのルートディレクトリを基準に)/stylesheets/style.cssへのlinkタグが記述されている。style.stylファイルをviews/stylesheetsに作成すると、そのStylusファイルをもとに生成されたCSSはpublic/stylesheetsに格納される。スタティックファイル配信のルートディレクトリにはpublicディレクトリが設定されているため、http://localhost:3000/stylesheets/style.cssへのHTTPリクエストには、public/stylesheets/style.cssファイルが返される。

また、今回ではStylusの機能をいくつか使用した。

@extendディレクティブは継承のコンセプトに基づいたディレクティブ。クラス(.padなど)を作成し、@expendディレクティブを使ってそのクラスのすべてのプロパティを他の要素に与える。例えば、ここでの@expendは以下のCSSを生成する。
*/





NodeのHTTPモジュールは素晴らしいが、より複雑なアプリケーションを簡単に開発したい場合にはExpresを利用することができる。Expressは数々のモジュールを統合して、短期間でWebアプリケーション開発を行うことができる柔軟なインターフェイスを提供する。

Expressプロジェクトの生成から始め、プロジェクトの構成を理解し、最後にはバックグラウンドのデータベースにMongoDBを使ったExpressアプリケーションを構築する。


Expressプロジェクトを作成する
Expressは、Nodeのモジュールとしてnodeのスクリプト内で動作するとともに、コマンドライン実行ファイルも提供する。Expressをコマンドラインで実行すると、さまざまなディレクトリとファイルで構成されたプロジェクトのテンプレートを出力する。このテンプレートをベースに、素早くアプリケーションを構築できる。

Expressモジュールは実行コマンドを持っているので、npmに-gオプションを付与してグローバルインストールする。

ターミナル
$ npm -g install express

Expressを利用してアプリケーションを構築する際に、アプリケーション名を決める必要がある。
今回アプリケーションの名前は、mimoeappにする。

ターミナル
$ express mimoeapp

このコマンドを実行すると、実行したディレクトリにmimoeappという新しいディレクトリが作成され、その中にさまざまファイルやディレクトリが生成される。

生成されたファイルの1つ、mimoeapp/package.jsonを開くと、mimoeアプリケーションのモジュール依存関係を参照できる。プロジェクト名(name)がデフォルトのままなので、mimoeappに変更しておく。

package.json
{
  "name": "mimoeapp",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "3.4.8",
    "jade": "*"
  }
}

dependenciesプロパティにexpressとjade(テンプレートエンジン)が記載されている。

プロジェクトをまとめて管理するために、これらの必要なモジュールはすべてプロジェクトフォルダ(mimoeapp)にインストールしておくことが重要。mimoeappディレクトリで以下のコマンドを実行する。

ターミナル
$ npm install

このコマンドは、mimoeappディレクトリに新しいnode_modulesディレクトリを作成し、その中にアプリケーションが依存するモジュールをインストールする。
これで、Expressを使った開発を行うための基本的な準備が完了した。


expressコマンドを実行すると、Expressを使った開発に適したディレクトリ構造とファイルが生成される。プロジェクトのルートディレクトリにはapp.jsとpackage.jsonの2つのファイルが生成される。

package.jsonはCommonJSによって提唱されたフォーマットで、NodeモジュールやNodeアプリケーションのパッケージ内容を記載するために一般的に利用される。
npm installコマンドは実行ディレクトリのpackage.jsonを読み込み、dependenciesプロパティから依存関係を割り出して、node_modulesディレクトリを作成しその中に必要なモジュールをインストールする。

ローカルインストールはアプリケーションの安定性のために重要な役割を果たす。Nodeのrequire関数は、実行ディレクトリを基準に階層をさかのぼってnode_modulesディレクトリを探す。実行中のディレクトリにインストールされているモジュールが上位階層にインストールされているモジュールより優先して読み込まれるため、上位階層にあるモジュールのバージョンが変更されている場合でも、アプリケーションが構築された時点と同じバージョンのモジュールを使い続けることができる。また、プロジェクトディレクトリにすべての依存関係が含まれているので、ディレクトリをそのままプロジェクトとして簡単に配布することもできる。


app.jsファイルはExpressプロジェクトの基礎であり、アプリケーションそのもの。アプリケーションは常に以下のコマンドで起動する。


ターミナル
$ node app.js

express(1)コマンドはこれらのファイルの他に、public、routes、viewsという3つのサブディレクトリを生成する。

publicディレクトリはスタティックファイルを格納するディレクトリ。このディレクトリは、スタティックファイル配信を行うexpress.staticミドルウェアのパラメータに渡されるディレクトリ。このディレクトリには、images、javascripts、stylesheetsという、それぞれのコンテンツを格納するサブディレクトリが生成されている。

routesディレクトリにはindex.jsファイルが生成されている。app.jsはrequireでindex.jsを呼び出している。URLルーティングを行うには、ルートのリクエスト時に実行する関数をindex,jsのexportsオブジェクトに追加する。routes/index.jsはサーバ機能を提供するコードとURLルーティングのためのコードを分離し、app.jsの肥大化を防ぐ。

viewsディレクトリには、テンプレートファイルが格納される。



app.jsの内容を読む
app.jsファイルを開き、そのコードを見てみる。

app.js
/**
 * Module dependencies.
 */

var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');

var app = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', routes.index);
app.get('/users', user.list);

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

最初に、expressメソッドを実行してExpressアプリケーションオブジェクトを作成し、app変数に格納している。このオブジェクトはアプリケーションに必要な変数やメソッドを保持し、提供する。

環境設定(app.~)が2か所で実行されている。最初の宣言は、アプリケーション全体の設定を行っている。次に呼び出されているのには、developmentが最初の引数として渡されている。ここでは開発時のみに適用する設定を行う。環境名を引数として渡すことで、環境によって設定を分岐させることができる(development、productionなど)。

アプリケーション全体の設定を行う最初のコールバックには、app.setを使ってデフォルトのviewディレクトリ(views)や、テンプレートエンジン(view engine)が設定されている。

次に、app.useを使ってexpress.urlencoded、express.methodOverride、app.router、express.staticの各ミドルウェア(function(req,res,next){}の形式を持った関数)が設定されている。

Expressは、favicon、logger、urlencodedなどのConnectの基準ミドルウェアをexpressアプリケーションオブジェクト(app変数)のメソッドとして格納している。例えば、urlencodedはexpress.urlencodedとして呼び出すことができる。

app.useに渡されたミドルウェアはアプリケーションスタックに追加される。クライアントからリクエストを受け取ると、そのリクエストはスタックに渡されて、スタックされている一連のミドルウェアで順番に処理される(そのため、app.useでスタックに渡す順番を考慮する必要がある)。

app.useで最初にスタックに追加されているfaviconは、ブラウザからのfaviconリクエストを処理する。loggerは受信したリクエストのログを出力する。デフォルトでは基本的なログをコンソールに出力するが、カスタマイズも可能。

methodOverrideは、擬似的に生成されたPUTやDELETEなどのHTTPリクエストを取り扱うことができるメソッド。ほとんどのブラウザは、HTMLフォームでGETとPOST以外のメソッドでフォームデータを送信することはない。そのため、フォームの送信時に以下のようなタグで_methodというhiddenデータを加えて、サポートされていないメソッドのリクエストを擬似的に生成する。

<input type="hidden" name="_method" value="DELETE">

HTTPプロトコルには、GETやPOST以外のさまざまなメソッドが規定されている。
(HTTPプロトコルのメソッドについてはhttp://www.w3.org/Protocols/rfc2616/rfc2616-sec9.htmlを参照する)
Expressは擬似メソッドを使ってブラウザがサポートしていないメソッドを処理するが、通常のHTTPメソッドを処理することもできる。

app.routerは、app.getやapp.postなどで定義されているすべてのルートを保持している。アプリケーションスタックで、app.routerを処理する順番が回ってくると、保持しているルートの処理を実行する。アプリケーションスタックにapp.routerが渡されていない場合は、app.getなどで定義されたルート(ルート定義自体がミドルウェア)が渡された順番でアプリケーションスタックの末尾に追加される。

Express(Connect)のミドルウェア(関数)は以下の構造を持っている。

function(req,res,next){
 //処理
 next();
}

nextパラメータはコールバックのような仕組みで、次に実行するミドルウェアを呼び出す。app.jsではapp.routerがexpress.staticの前に配置されているため、リクエストが行われると、app.routerに格納されている(app.getやapp.postなどで設定した)ルートに合致するかどうかを判定する。app.routerでルートが発見されずnext()呼ばれた場合にのみ、express.staticでスタティックファイルを探す。



routes/index.jsの内容を読む
routes/index.jsはapp.jsからrequireで呼び出される。

app.js
var routes = require('./routes');

index.jsを直接指定していないが、requireにディレクトリが渡された場合、Nodeは自動的にindex.jsを探す。
(requireメソッドが読み込むファイルを判断するステップについては、http://nodejs.org/api/modules.html#modules_all_togetherを参照にする)

routes/index.js
/*
 * GET home page.
 */

exports.index = function(req, res){
  res.render('index', { title: 'Express' });
};

exports.indexにルートを処理する関数を定義している。routes/index.jsはapp.jsで呼び出されてroutesオブジェクトに格納される。次のapp.get文にroutes.index関数が渡され、実行時にroutes/index.jsのexports.indexが呼び出される。

app.js
app.get('/', routes.index);

routes.indexは、基本的にhttp.createServerのコールバックと同じ構造だが、app.getの最初にパラメータに渡されたルートに対してのみ実行される。また、リクエスト(req)とレスポンス(res)パラメータはExpressによって拡張されている。

routes.indexはres.renderメソッドを呼び出している。titleプロパティで文字列を渡して、views/index.jadeから読み込んだテンプレートをもとにページを生成して返す。



環境の定義と設定
開発コードと本番コードでは、異なる設定が求められる。例えば、開発中はクライアントに対してデバッグ用の詳細なエラーメッセージを返したいが、本番環境では公開するエラーメッセージの情報を最小限に押さえたい場合などが考えられる。

Expressではapp.configureを使って、そのようなケースに対処することができる。複数の実行環境を設定し、それぞれの環境に異なった設定を行うことができる。

Expressが作成したapp.jsの中身を見てみる。

app.js
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

環境名文字列を引数として渡すと、それがNODE_ENV環境変数(app.get('env')、もしくはprosess.env.NODE_ENV)とマッチした場合に、2つ目の引数として渡されているコールバック関数を実行する。

expressコマンドでプロジェクトを作成した時点では、NODE_ENV環境変数にdevelopmentが入力された場合の設定を行う準備ができている。app.jsに以下の設定を追記すると、環境変数にproductionを指定して起動した場合はdevelopment向けの設定を無視してproduction向けのコールバックで記述した設定を行う。

app.js
if ('production' == app.get('env')) {
  app.set('port',8000);
}


Expressは、ワークフローごとの設定を分離する機能を提供する。環境名の文字列を渡すだけで、環境に応じた設定を使い分けることができる。

Expressは環境名を判断するために、process.envを利用してNODE_ENV環境変数の取得を試みる。NODE_ENVが設定されている場合はその値を、設定されていない場合はデフォルト値としてdevelopmentを使用する。

NODE_ENV環境変数にproductionを設定してアプリケーションを起動するには、以下のコマンドを実行する。

ターミナル
$ NODE_ENV=production node app.js



新たな環境を設定する
アプリケーションの開発中は、本番環境では使用しない特別な設定を行うことによって、よりスムーズに作業を行うことができる。例えば、本番環境に近いステージング環境の設定を開発用マシンで整えたい場合などに、環境設定の分岐を利用することができる。

本番サーバ上ではアプリケーションが特定のポート(80番)で動作するものの、開発用サーバでは、root権限を持たないため80番で動作させることができない場合、本番環境と同じ設定を行ったステージング環境を用意し、ポート番号だけを80番以外の番号に変更して最終テストを行うことができる。

app.jsに本番環境(production)とステージング環境(staging)を加えて、環境によってポート番号を変更するように設定する。

app.js
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

if ('staging' == app.get('env')) {
  app.use(express.errorHandler());
//ステージング環境の設定(本番環境の設定と同じ)
}

if ('production' == app.get('env')) {
  app.use(express.errorHandler());
  //本番環境の設定
  app.set('port',80);
}

全環境向けの環境設定ではポート番号が3000番(もしくは環境変数PORTの値)に設定されているが、本番環境では80番ポートを使用するように設定した。これを確認するために、アプリケーションを実行してみる。以下のように本番環境を起動する。

ターミナル
$ sudo NODE_ENV=production node app.js
Password:★入力する
Express server listening on port 80

実行時には、80番ポートで他のアプリケーション(httpdなど)が動作していないかどうかを確認する。



NODE_ENVを永続的に変更する
アプリケーション開発がステージングの段階に入って動作確認する際に、毎回ENV_NODE=stagingを入力していると手間がかかる。本番環境(production)の場合も同じ。本番環境の場合にサーバを再起動する回数は少ないが、起動するときにNODE_ENVを指定することを覚えておく必要がある。

ターミナル
$ export NODE_ENV=staging

この設定は、コマンドを実行したターミナル上で、ターミナルが開いている間有効。この設定を常時したい場合は、ランコマンド(rc)ファイルに書き込んでおく必要がある。シェルの種類により、rcファイルの名前は異なる。bashの場合は~/.bashrc(~はホームディレクトリ)、shの場合は~/.shrc、kshの場合は~/.kshrcなどとなる。

NODE_ENVにstagingを設定し、rcファイルに書き込む場合は以下のコマンドを発行する(bashの場合)。

ターミナル
$ echo -e "export NODE_ENV=staging\n" >> ~/.bashrc



ダイナミックURLルーティング
ExpressはさまざまなURLルーティングの方法を凌駕したパワフルなインターフェイスを提供する。Expressで動的なURLルーティングを設定する。

mimoeappディレクトリとその中身を使う。

Mr.Pageという架空のキャラクターのページを/pageに追加する。app.jsでURLのルートを設定している場所(app.get('/',routes.index)などがある場所)に次のコードを挿入する。

app.js
app.get('/', routes.index);
app.get('/page',function(req,res){
res.send('こんにちは。Mr.Pageです。');
});

ターミナル
$ node app.js
Express server listening on port 3000

http://localhost:3000/pageにアクセスすると、
こんにちは。Mr.Pageです。になる。

リクエストされたルートの一部をreq.paramsに格納して利用することもできる。同じ場所に下記のコードを追加して確かめる。

app.js
app.get('/:page',function(req,res){
res.send('こんにちは。Mr.' + req.params.page + 'です。');
});

/以下の文字列が:pageプレースホルダによって、req.params.pageに格納される。例えば、http://localhost:3000/mimoeにアクセスすると、
こんにちは。Mr.mimoeです。になる。

各ルートで返される値を直接app.jsに記述しても動作する。しかし、app.jsスクリプトの肥大化と複雑化を避けるために、通常はコールバック関数の内容を分離して、routes/index.jsに記述する。

routes/index.js
exports.mrpage = function(req,res){
res.send('こんにちは。Mr.Pageです。');
};

exports.anypage = function(req,res){
res.send('こんにちは。Mr.' + req.marams.page + 'です。');
};

app.jsがこれらのコールバック関数を呼ぶよう修正する。

app.js
app.get('/page', routes.mrpage);
app.get('/:page', routes.anypage);

コード変更前と同様の動作が実現できる。

app.getを使用して、/pageへの固定ルートを設定し、このルートへのアクセスがあった場合のサーバの処理をapp.getのコールバックに記述する。今回は、res.sent(httpモジュールのres.writeを拡張したメソッド)を使ってシンプルなテキストを出力している。

Expressは固定ルート設定の他にも、プレースホルダを使ったフレキシブルなURLルーティング機能を持っている。プレースホルダの例として、コードに:pageプレースホルダを記述してルーティングを行っている(:はプレースホルダを表し、その後に続く文字列はプレースホルダの名前)。

リクエストURLがプレースホルダの形にマッチすると、プレースホルダにあたる文字列がreq.paramsのパラメータとして格納される。その際のパラメータ名は:を除いたプレースホルダの名前(page)。本コードの場合は、例えば/NodeCookbookにアクセスがあると、req.params.pageにNodeCookbookという文字列が格納される。

ブラウザでhttp://localhost:3000/pageにアクセスすると「こんにちは。Mr.Pageです。」と表示され、http://localhost:3000/NodeCookbookにアクセスすると「こんにちは。Mr.NodeCookbookです。」と表示される。



ルートのバリエーション
プレースホルダを使ったURLルーティングを行う場合、プレースホルダに格納する文字列を、正規表現を使って制限できる。例えば以下のようなコードを記述することができる。

app.js
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.set('case sensitive routing',true);      //追加



app.get('/', routes.index);
app.get('/:page([a-z]+)',routes.anypage);  //修正


pageプレースホルダに文字列マッチングを行い、小文字のアルファベットで構成された文字列に対してのみroutes.anypageコールバック関数を呼び出す。ただし、ルート判定の文字列で大文字小文字を認識するためには、環境設定内で、app.setでcase sensitive routingの設定を行っておく必要がある。

app.set('case sensitive routing',true);

http://localhost:3000/nodecookbookは「こんにちは。Mr.NodeCookbookです。」と返すが、http://localhost:3000/NodeCookbookは404エラーを返す。



オプションルート
ルートで呼び出すコールバック関数を使って、オプションルートを設定することができる。以下のコードをルート設定に追記する。

app.js
app.get('/:page/:admin',routes.anypageAdmin);

routes/index.jsに以下のコールバック関数を記述する。

routes/index.js
exports.anypageAdmin = function(req,res){
var admin = req.params.admin;
if(admin){
if(['add','delete'].indexOf(admin)! == -1){
if(admin === 'add'){
admin = '追加';
}else{
admin = '削除';
}
res.send(req.params.page + 'ページを' + admin + 'したいですか?');
return;
}
res.send(404);
}
}

routeの:adminプレースホルダに文字列が存在するかどうかを確認する。存在する場合は、その文字列が想定した文字列(addもしくはdelete)かどうかを判別して、それぞれに対応したレスポンスを返す。指定されている文字列ではない場合は404エラーを返す。


http://localhost:3000/nodecookbook/add   にアクセスすると
nodecookbookページを追加したいですか?    となり、 http://localhost:3000/nodecookbook/delete  にアクセスすると
nodecookbookページを削除したいですか?    となる。


この例では、指定する文字列が増えた場合にも容易に拡張できるが、拡張を行う必要性がない場合は、正規表現を使って下記のような記述も使用できる。

app.js
app.get(/^\/(\w+)\/(add|delete)?$/,routes.anypageAdmin);

routes/index.js
exports.anypageAdmin = function(req,res){
  res.send(req.params.page[0] + 'ページを' + req.params.admin[1] + 'したいですか?');
}




ワイルドカード
ルート指定時にアスタリスク(*)をワイルドカードとして使用できる。例えば次のようなルートを設定する。

app.js
app.get('/:page/*',routes.anypage);   //修正

routes.anypageは次のように記述する。正規表現やワイルドカードで取得された、プレースホルダの名前を持たないパラメータは、req.params[n]で指定することができる。

routes/index.js
exports.anypage = function(req,res){
var subpage = req.params[0];
var parentPage = subpage ? req.params.page + 'の' : ' ';
res.send(parentPage + (subpage || req.params.page) + 'へようこそ!');
};

http://localhost:3000/Node/Cookbookにアクセスすると、「NodeのCookbookへようこそ!」と表示される。http://localhost:3000/Node/にアクセスすると「Nodeへようこそ!」と表示される。

以下のようにワイルドカードを使うと、pageという文字列を含むすべてのURLがMr.Pageのページになる。

app.js
app.get('/*page*',routes.mrpage);





Nodeクックブック p.163
Webの開発中は、エディタで小さな変更を行い、ファイルを保存して、必要な場合はファイルをアップロードして、ブラウザをリロードして結果を確認する、というサイクルを繰り返すことがあると思う。では、ファイルを保存するたびに修正済みのファイルをブラウザが自動的に読み込む「開発ツール」があれば便利ではないだろうか。

これをfs.watchとWebSocketを使って実現することができる。fs.watchはディレクトリを監視して、ディレクトリ内のファイルに変更が入り次第コールバック関数を実行する(サブディレクトリの監視は行わない)。

この「開発ツール」は、PHPからスタティックファイルまで、どのようなフレームワークでも使用することができる。
新しいディレクトリを作成し、server.jsというファイルを作成しておく。同じディレクトリ内にcontentという名前のディレクトリを作成し、その配下に配信用ファイル、index.htmlscript.jsstyles.cssを作成する。
開発ツールのスクリプトとしてwatcher.jsというファイルを作成する。

server.js
var http = require('http');
var path = require('path');
var fs = require('fs');

var mimeTypes = {
'.js': 'text/javascript',
'.html': 'text/html',
'.css': 'text/css'
};

http.createServer(function (request, response) {
var lookup = path.basename(decodeURI(request.url)) || 'index.html',
f = 'content/' + lookup;
fs.exists(f, function (exists) {
if (exists) {
fs.readFile(f, function(err, data) {
if (err) {
response.writeHead(500);
response.end('Server Error!');
return;
}
var headers = {'Content-Type': mimeTypes[path.extname(f)]};
response.writeHead(200, headers);
response.end(data);
});
return;
}
response.writeHead(404);
response.end('ページがみつかりません!');
});
}).listen(8080);

index.html
<html>
<head>
<title>Yay Node!</title>
<link rel="stylesheet" href="styles.css" type="text/css">
<script src="script.js" type="text/javascript"></script>
<script src="http://localhost:8081/socket.io/watcher.js"></script>
</head>
<body>
<span id="yay">イェイ!</span>
</body>
</html>

script.js
window.onload = function() { alert('Node最高!'); };

styles.css
#yay {font-size:5em;background:blue;color:yellow;padding:0.5em}

watcher.js
var fs = require('fs');
var io = require('socket.io').listen(8081);
var sioclient = require('socket.io-client');

var watcher = [';(function () {',
'var socket = io.connect(\'ws://localhost:8081\');',
'socket.on(\'update\', function () {',
' location.reload();',
'});',
'}());'].join('');

sioclient.builder(io.transports(), function (err, siojs) {
if (!err) {
io.static.add('/watcher.js', function (path, callback) {
callback(null, new Buffer(siojs + watcher));
});
}
});

fs.watch('content', function (e, f) {
console.log(e + ' ' + f);
//if (f[0] !== '.') { // MacOS Xではこのif文を削除
io.sockets.emit('update');
// }
});

ポートの競合を避けて8081番にsocket.ioサーバを生成し、クライアント側で使用するコードをwatcher変数に文字列として格納する(通常はファイルから読み込まれるが、簡単な開発ツールなので、ここでは変数に文字列をそのまま格納している)。この文字列をsocket.io.jsと組み合わせて、watcher.jsとしてクライアントに配信する。

スクリプトの最後でfs.watchメソッドを使ってcontentディレクトリを監視する。コールバックにはイベントとファイル名をそれぞれeパラメータとfパラメータで渡す。

ここでファイル名がドットで始まっていないか(隠しファイル)を確認する。OSやエディタによっては、ファイルの保存と同時に隠しファイルを更新する場合がある。隠しファイル
の更新をトリガーにすると予期しないイベントが複数発生し、問題を引き起こす可能性がある。

このツールを利用するには、監視の対象となる(HTML)ファイルに以下のscriptタグを埋め込む(通常は、サーバサイドテンプレートなどで自動的な埋め込みを行うが、ここではそれぞれのファイルに自分で追記する)。

<script src="http://localhost:8081/socket.io/watcher.js"></script>

server.jswatcher.jsを実行し、サーバを立ち上げる。ブラウザでhttp://localhost:8080にアクセスすると、「イェイ!」ページが表示される。contentディレクトリ内でファイルを保存、もしくは新たなファイルを追加すると、index.htmlが即座にリロードされ、変更内容が反映される。



Nodeクックブック p.159
socket.ioは、設定オプションの豊富さと考え抜かれたメソッド設計によって、非常に柔軟に利用できるライブラリ。

リモートサーバ接続者の数をリアルタイムで更新するウィジェットを作成して、socket.ioの器用さを試してみる。このウィジェットをライブオンラインカウンタ(LOC)と名づける。

どのWebサイトにも簡単に設置でき、どのユーザでも簡単に使えて、動作させるために特別な知識を必要としないよう、LOCのインターフェイスはとてもシンプルにする。scriptタグでWebページにロードし、initメソッドを呼び出す(初期化の前にプロパティを読み込むことができる)。


準備
新しいディレクトリを作成し、
新しいファイル
widget_server.js
widget_client.js
server.js
index.html

を準備する。

ここにsocket.ioモジュールをインストールしておく。

ターミナル
$ npm install socket.io

加えて、socket.io-clientモジュールをインストールしておく。
このモジュールを使ってsocket.ioのクライアント側のコードをカスタマイズする。

ターミナル
$ npm install socket.io-client


手順
必要なインターフェイスをindex.htmlに記述する。

index.html
<html>
<head>
<style>
#_loc {color:blue;} // ウィジェットのカスタマイズ項目
</style>
</head>

<body>
<h1>俺のWebページ</h1>
<script src="http://localhost:8081/loc/widget.js"></script>
<script>locWidget.init();</script>
</body>
</html>

widget_client.jsにウィジェットを記述する。

widget_client.js
window.locWidget = {
style: 'position:absolute;bottom:0;right:0;font-size:3em',
init: function () {
var socket = io.connect('http://localhost:8081/', {resource: 'loc'});
var style = this.style;
socket.on('connect', function () {
var head = document.getElementsByTagName('head')[0];
var body = document.getElementsByTagName('body')[0];
var loc = document.getElementById('_lo_count');
if (!loc) {
head.innerHTML = '<style>#_loc {' + style + '}</style>' + head.innerHTML;
body.innerHTML += '<div id="_loc">オンライン:<span id="_lo_count"></span></div>';
loc = document.getElementById('_lo_count');
}
socket.on('total', function (total) {
loc.innerHTML = total;
});
});
}
}

ウィジェットをテストするために、複数のドメインからサイトにアクセスする必要がある。そこで、index.htmlを配信するHTTPサーバを生成する。このサーバはローカル環境で実行すると、http://localhost:8080とhttp://127.0.0.1:8080として接続できるため、擬似的に複数ドメインからの接続をテストできる。

server.js
var http = require('http');
var clientHtml = require('fs').readFileSync('index.html');

http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/html'});
response.end(clientHtml);
}).listen(8080);

最後に、widget_server.jsにウィジェットサーバを記述する。

widget_server.js
var io = require('socket.io').listen(8081);
var sioclient = require('socket.io-client');
var widgetScript = require('fs').readFileSync('widget_client.js');
var url = require('url');

var totals = {};

io.configure(function () {
io.set('resource', '/loc');
io.enable('browser client gzip');
});

sioclient.builder(io.transports(), function (err, siojs) {
if (!err) {
io.static.add('/widget.js', function (path, callback) {
callback(null, new Buffer(siojs + ';' + widgetScript));
});
}
});

io.sockets.on('connection', function (socket) {
var origin = (socket.handshake.xdomain) ? url.parse(socket.handshake.headers.origin).hostname : 'local';
totals[origin] = (totals[origin]) || 0;
totals[origin] += 1;
socket.join(origin);
io.sockets.to(origin).emit('total', totals[origin]);
socket.on('disconnect', function () {
totals[origin] -= 1;
io.sockets.to(origin).emit('total', totals[origin]);
});
});

2つのターミナルウィンドウを開いてテストする。最初のウィンドウでウィジェットサーバを立ち上げる。

ターミナル
$ node widget_server.js

2つ目のウィンドウでHTTPサーバを立ち上げる。

ターミナル
$ node server.js

ブラウザでhttp://localhost:8080にアクセスし、別のタブかウィンドウを開いて同じくhttp://localhost:8080にアクセスする。すると、カウンタが1増加するのがわかる。どちらかを閉じると、カウンタが1減少する。さらに、別のタブかウィンドウで別ドメインのhttp://127.0.0.1:8080にアクセスする。http://localhost:8080のカウンタとは関係なく、独自のカウンタの数字が表示される。


解説
widget_server.jsがこのLOCの中心的役割を果たしている。このスクリプトでは、最初にsocket.ioをrequireで呼び出して、listenメソッドを使って8081番ポートでクライアントの接続を待機する。ここでは、listenメソッドにhttpServerインスタンスを渡すことはせず、ポート番号を直接渡す。ws://localhost:8081は、ウィジェットを通してクライアントが接続するサーバ。

次に、socket.io-clientをrequireでsioclientに呼び出している。
sioclientを通してsioclient.builderメソッドにアクセスし、socket.io.jsファイルをカスタマイズして生成する。

sioclient.builderメソッドには2つの引数を渡す。最初の引数はio.transport()で、WecSocketやxhrポーリングなど、リアルタイム通信を実現する様々な手段の配列。配列の先頭に近いほど優先度が高い手段。listenメソッドでオプションを設定していないため、メソッドの戻り値はデフォルトの配列(websocket、htmlfile、xhr-polling、jsonp-pollingの順)。

2つ目の引数はコールバック関数。siojsパラメータにsocket.io.jsの内容が渡されるので、これをコールバック内で使うことができる。次に述べるio.static.addのメソッドで、siojsの内容が
widget_client.jsを読み込んだwidgetScript変数の内容とつなげられて、両方のJavaScriptコンテンツを含んだ1つのファイル(widget.js)が生成される。これらのスクリプトがお互いに影響しないよう、間にセミコロンをはさむ。

socket.ioは、クライアント用JavaScriptファイルを配信するためのHTTPサーバを内部に持っている。HTTPサーバを新たに生成する代わりに、io.static.addメソッドを使ってsocket.ioの内部HTTPサーバに新しいURLルートを設定する。io.static.addの2つ目のパラメータにはコールバック関数が渡される。

このコールバック関数は、追加されたURLルートで配信するコンテンツを設定する。最初の引数は物理ファイルを参照するが、今回は動的にコードを生成するのでnullを渡す。2つ目のパラメータは、(widget.jsとして配信される)siojsとwidgetScriptのコンテンツをつなげた内容をBufferに入れて渡している。これで/widget.jsルートが設定された。

io.configureでresourceプロパティを変更すると、socket.ioが生成する内部HTTPサーバのルートディレクトリを変更することができる。ここではスクリプトファイルへのアクセスを、デフォルトのhttp://localhost:8081/socket.io/widget.jsではなく、ウィジェットの名前(LOC)をパスに含んだ
http://localhost:8081/loc/widget.jsでできるように、resourceプロパティの値を設定している。

クライアントから/loc/widget.jsに接続するために、
widget_client.jsのinitメソッド内のio.connectの2つ目のパラメータにoptionsオブジェクトを渡しておく必要がある。optionsオブジェクトにはファイルへのパスを示すresourceオプションが設定されているが、(widget_server.jsのオプションに設定したように)スラッシュがついていないことに注意する。サーバ側ではスラッシュが必須だが、クライアント側ではスラッシュを入れてはいけない。

これでようやくソケット通信を始める準備が整った。
widget_server.jsは、io.socketsのconnectionイベントを待機している。そのイベントハンドラでは、まだ紹介していないsocket.ioの機能を使っている。

WebSocketは、クライアントHTTPリクエストを通してハンドシェイクを要求し、サーバがその要求に答えることによって確立する。sockt.handshakeはそのハンドシェイクのプロパティを格納している。
socket.handshake.xdomainは、ハンドシェイクが同じサーバと確立されたかどうかを示す。このプロパティをsocket.handshake.headers.originのhostnameを取得する前にクロスドメインのハンドシェイクかどうか確認するために使っている。

同じドメインへのハンドシェイクのoriginはnullもしくはundefined(ローカルファイルのハンドシェイクか、localhostのハンドシェイクかによる)。undefinedやnullはurl.parseに渡すとエラーが発生するので、同じドメインのハンドシェイクにはlocalという文字列を与える。

originの値を抽出するのは利用しているWebサイトを区別するためで、これによりサイトごとのリアルタイム訪問者カウントを行う。

totalsオブジェクトを使ってカウントを保持する。新しいoriginが発生するたびに、originの値を初期値0のプロパティとしてtotalsオブジェクトに追記する。connectionイベントが発生するたびに、totals[origin]の値に1を追加する。disconnectイベントも待機しておき、発生するたびに1を引く。

totalsの値がサーバでしか使えない場合は、このソリューションは完成しているとは言えない。これらの値をサイトごとに、サイトに接続しているブラウザに提供する方法が必要。

socket.ioのバージョン0.7以降では、socket.joinメソッドを使って複数のソケットのグループを部屋(Room)に分ける便利な機能を用意している。接続中のソケットをoriginの文字列が名前に与えられた部屋に分けて、io.socket.to(origin).emitメソッドを使って、イベントをemitする際に同じsitesの部屋に入ったソケットにのみイベントを送信するよう、socket.ioで設定する。

io.socketsのconnectionイベントとsocketのdisconnectイベントそれぞれの発生時にtotalカスタムイベントを発生させて、それぞれのサイトの訪問者のソケットにトータル接続数を伝える。

widget_client.jsはdivを生成し、totalイベントを受信するたびにdivの内容を書き換える。




Nodeクックブック p.153