カンタン! Express.js を拡張する超詳細手順

こんにちは。Tokyo Otaku Mode エンジニアの重岡です。

本ブログを読んだことがある方々は既にご存知かもしれませんが、Tokyo Otaku Modeでは otakumode.com に Node.js と MongoDB を採用しています。フレームワークには Node.js 製の Express.js (3系) を使っています。Express.js は Ruby の Sinatra や Python の Flask と同じような MicroFramework なので、必要な機能は npm で module をインストールする、もしくは自分で Express.js の機能を拡張します。

今回は Express.js に機能拡張した一例として res.render を otakumode.com 向けに使いやすく改良した手順をご紹介します。

View 出力時の共通処理をまとめたい

View を出力するときに毎回、同じ処理を書いていることってありますよね。
例えば、PCとモバイルで別々のテンプレートファイルを使いたい場合や、システムで管理している Facebook の OGP 設定を View を出力前に取得して使うという内容です。
otakumode.com では、Express.js の res.render を拡張して、共通処理をまとめています。

Express.js の res.render を拡張する手順

otakumode.com では下記のような手順で res.render を拡張しています。

  1. express.responseres.render を別の変数 res.originalRender に退避する
  2. res.render を上書きして、その中で res.originalRender を呼ぶ
  3. res.originalRender の前に View 出力前の共通処理を追加する

箇条書きにしてみると、やってることはすごくシンプルなことが分かりますね。
実際にコードを見てみましょう。

res.render の拡張

res.render を拡張しているベースのコードです。
上書きした res.render の中で、別ファイルに定義しているフィルター処理 smartPhoneFilter を呼び出して、最後に res.originalRender を実行しています。

lib/filter/response/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var res = require('express').response;
var smartPhoneFilter = require('./smart_phone');
// res.render を上書きする前に、元のメソッドを退避する
res.originalRender = res.render;
// res.render を上書き
res.render = function(view, opts, fn) {
var _this = this;
var options = {
view: view,
opts: opts,
fn: fn
};
// Fileterをつないでいく
var filters = [
smartPhoneFilter
// 他に Filter があればここに追加する
];
var find = 0;
(function() {
if (find < filters.length) {
var filter = filters[find++];
return filter(_this, options, arguments.callee);
} else {
return res.originalRender.call(_this, options.view,
options.opts, options.fn);
}
})();
};

モバイル用 View ファイルを利用する res.render の拡張

次に、res.render で呼び出していたフィルター smartPhoneFilter のコードを見てみましょう。

モバイル端末からのアクセスを判別する res.isSmartPhone を元に、モバイル用の View ファイルが存在する場合は、それを res.render で利用する処理を追加しています。
具体的には、以下の様な方針で View ファイルを設置しています。

  • PC用の View ファイルは views/ 以下に設置する
  • モバイル用の View ファイルは views/m/ 以下に PC 用と同じディレクトリ構造とファイル名で設置する
lib/filter/response/smart_phone.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var res = require('express').response;
var fs = require('fs');
var VIEWS_DIR = __root + '/views/';
// response拡張
res.setSmartPhone = function(flag) {
this.isSmartPhone = flag;
};
/**
* スマートフォンからアクセスがあった場合はテンプレートを書き換える
*
* - Options
* view
* opts
* fn
*
* @param {ServerResponse} ServerResponseのインスタンス
* @param {Object} res.renderで渡ってくる引数をまとめたオブジェクト
* @param {Function} コールバック
*/
module.exports = function(res, options, next) {
if (res.isSmartPhone) {
var path = 'm/' + options.view;
if (path.indexOf('.jade') === -1) {
path += '.jade';
}
return fs.stat(VIEWS_DIR + path, function(err) {
if (!err) {
options.view = path;
}
return next();
});
}
return next();
};

ちなみに、res.isSmartPhonereq.headers['user-agent'] を元に判別する処理を記述しており、他の場所で値をセットしています。

res.render の拡張を利用する

最後に、実際に res.render の拡張を利用するには、 app.js で今回、定義した response の拡張コードを読み込めば OK です。

1
require(__root + "/lib/filter/response");

app.useapp.engine を利用するアプローチ

今回ケースに限っていうと smartPhoneFilter の中で response を利用しているので、res.render を上書きしています。

他のアプローチとして、app.useapp.engine を使用して Express.js の 挙動をカスタマイズをする方法があります。このアプローチの場合、リクエストハンドラ等から参照する必要のある request オブジェクトや response オブジェクトに対する処理は app.use に、テンプレートエンジンの挙動に関する処理は app.engine に記述します。

app.use による request オブジェクトの書き換え例

1
2
3
4
app.use(function(req, res, next) {
req.isSmartPhone = ((req.headers['user-agent'] || '').search(/iPhone|iPod|Android/) > 0;
next();
});

app.engine によるテンプレートエンジンの挙動の変更

1
2
3
4
5
6
7
8
9
10
var express = require('express');
var app = express();
var jade = require('jade');
app.engine('jade', function(view, opts, fn) {
if (opts.req.isSmartPhone) {
view = 'm/' + view;
}
return jade.__express(options.view, options.opts, options.fn);
});

まとめ

どうでしたか? とってもカンタンに Express.js に機能を拡張できることが分かってもらえたのではないでしょうか。Express.js は軽量なフレームワークなので、不便に感じたら今回のように機能を拡張したりソースコードを読んだりして、開発しています。

Tokyo Otaku Mode では Express.js を使ってシステム開発をしてくれるエンジニアを募集しています。ご興味がありましたら、こちらからご応募ください。