express-generatorを使って、スケルトン作ってサンプル動かしたはいいものの、app.jsを見て、あー仕組み調べるのめんどくさって思った方のための記事です。nodeもexpressも初心者のiOSエンジニアが書いてますので、同じような状況の方にとって有益かと思います。
以下で、app.jsを上から順に読んで、必要なリファレンスへのリンクを載せたり、ざっくりこういうことしてるってことを書きます。
app.js (express ver4.12.3の場合)
ソースはなにもしなければ、こんな感じになっていると思います。
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var users = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', routes);
app.use('/users', users);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
解説
上から順に見ていきます。
各種nodeモジュールをインスタンス化
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
ここでまず知らないといけないのは、requireがどう動くか。
requireに関して
requireで読み込めるモジュールは3タイプあるようです:
-
Core Modules ... nodeのビルトインモジュール。nodeのソースの
lib/
配下にある。 - File Modules ... 1で見つからなければ、同名のファイル(.js/.json/.node)を読み込みます。
-
Loading from node_modules Folders ... 1,2で見つからなければ、同ディレクトリに配置された
node_modules
から読み込みます
上記の中では、require('path')
だけが1のタイプで、残りは3のタイプになります。
3のタイプのモジュールは、通常package.json
の、dependenciesの部分に記載して、npm installしないと、node_moduleの中にソースが入ってきません。express-generatorでは自動で生成してくれているので省略できています。
ちなみに、1のタイプに関しては、nodebrewのバイナリでnodeを入れた人は、ソースないはずなんで、確認したい場合はnodeのgitリポジトリをみるといいかもしれません。
※ 上記でrequireしているモジュール一覧
- express : nodejs用のwebアプリ作成フレームワーク
- path : ファイルパスを扱うユーティリティ
- serve-favicon : ファビコンを表示する
- morgan : HTTPリクエストのログを吐き出す
- cookie-parser : クッキーをパースする
- body-parser : リクエストパラメータのパーサーを設定する
※ requireに関して、詳しくはnodeの公式ドキュメントをご覧ください。
ミドルウェアとは
expressフレームワークの中では、モジュールのことをミドルウェア(middleware)と呼んでいるようで、下記のように定義されています(意訳です)。
ミドルウェアはリクエストオブジェクト(req)、レスポンスオブジェクト(res)、そして次のミドルウェアへのアクセスをもった一機能であり、下記のことができます:
- 任意の処理を実行できる
- リクエストやレスポンスオブジェクトに対して変更を加えられる
- リクエスト-レスポンスサイクルを終了できる
- 次のミドルウェアへ処理を渡すことができる
詳細に関しては、
- 公式ページ
-
逆引きメモ:expressの使い方
を参照するといいです。
ルーティングモジュールを読み込む
var routes = require('./routes/index');
var users = require('./routes/users');
このrequireは、上記タイプの2に相当します。'./' から始まる場合は、require() を呼んだファイルからの相対パスになります。では、index.jsの中を見てみましょう。
index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
ここでは、各pathに対するリクエストに対して任意の処理を行い、最終的にrouterオブジェクトをapp.jsに返しています。
express.Router()とは
公式リファレンスによると、var router = express.Router([options]);
という形で使い、ミドルウェアを実行したりルーティングを実行するメソッド群と考えて下さいとのこと。optionsには、大文字小文字の区別をするかとか、'/'の有無を区別するかとかを設定することができるようです。
router.getとは
router.getでは、router.METHOD(path, [callback, ...] callback)
という形で、特定のpathに来たリクエストに対しての処理を記述することができます。METHODで取れるのは、HTTPメソッドのそれと同じですが、小文字で書くルールです。
router.METHODは、後述するapp.use([path,] function [, function...])とほぼ同じような動きをするのですが、内部でnext()を呼ばなくても、次のrouteへ処理を渡してくれる点が異なります。
res.renderとは
res.render(view [, locals] [, callback])という形で用い、viewパラメータにviewテンプレート名を指定し、localsパラメータには、連想配列を渡します (今回は、{ title: 'Express' }
となっているので、indexテンプレートのtitle変数に'Express'が渡ります)。
module.exportsとは
ルーティングを設定したrouterオブジェクトを返しています。詳細に関しては、公式ドキュメントを参考にしてみてください。
アプリケーションのインスタンスを作ります
ここで作成したインスタンスに後述するapp.getとかapp.setなどの処理を行っていきます。
var app = express();
app.set(name, value)
app.set(name, value)でアプリケーションの各種設定ができます。
'name'
は、app setting tableで予め定義されているのでご注意ください。
ここでは、viewテンプレートが入ったフォルダと、viewテンプレートエンジンを設定しています。
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use([path,] function [, function...])
app.useを使って、指定のpathにきたリクエストに対して、処理を記述できます。
が、app.use()のパラメータにミドルウェアがかんでいる場合は、パラメータが必ずしも[path,] function [, function...]
になるとは限りません。ミドルウェアの内部に処理が隠蔽されているためです。また、先述のrouterの部分で、next()を呼ばないといけないと書きましたが、ミドルウェアの場合はそれも内部で任意に処理されているので、注意が必要です。
ファビコンを設定
デフォだとコメントアウトされていますが、ファビコンを /public/favicon.ico
に配置したらコメントアウトをはずしてね、と丁寧にかいてあります。
// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
ログ出力方法を設定
app.use(logger('dev'));
パラメータのパース方法を設定
app.use(bodyParser.json());
パラメータのパースで利用するライブラリを設定
app.use(bodyParser.urlencoded({ extended: false }));
urlエンコードの際に、querystringライブラリを使うか、qsライブラリを使うかの設定。falseが前者。
クッキーのパーサーを設定
app.use(cookieParser());
静的ファイルが配置されているディレクトリを設定
app.use(express.static(path.join(__dirname, 'public')));
express.static(root, [options]) は、expressにビルトインの唯一のミドルウェアで、rootディレクトリ配下に配置している静的ファイルへのルーティングを自動で行ってくれます。
例えば、myapp/public/images/hoge.png
や/public/license.html
といったものにアクセスすることができるようになります。
ルーティング設定を適用する
app.use('/', routes);
app.use('/users', users);
こういうふうに1階層まではapp.jsに書いて、残りは各ルーティングモジュールに任せるという設計かなと思います。
エラーハンドリング
HTTPステータス404エラー
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
errorオブジェクトを作成し、next(err)
で次のミドルウェアに処理を渡しています。
development環境でのエラー出力処理
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
envは、デフォルトでは process.env.NODE_ENV
か “development”
に設定されています。
app.jsでは、何も設定していないので、このスコープに入ってくるようになっています。
参考: http://expressjs.com/api.html#app.set
app.useの基本形は、app.use([path,] function [, function...])
で、pathが省略されている場合は、'/'になります。つまり、どの階層でエラーが起きても、ここに入ってくるということです(多分)。
で、res.renderのところは、前述のres.renderとはと同じです。
production環境でのエラー出力処理
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
エラーメッセージのみで、errorの詳細は吐出されません。
モジュールとして本アプリを出力
module.exports = app;
本app.js自体も、外部モジュールとして利用できるようになっている、ということでしょうか。。
最後歯切れわるかったですけど、こんな感じです。間違いや、こうだよということがあればご指摘お願いします!