node初心者がexpress-generatorで吐き出されたapp.jsを読んでみる

More than 3 years have passed since last update.

express-generatorを使って、スケルトン作ってサンプル動かしたはいいものの、app.jsを見て、あー仕組み調べるのめんどくさって思った方のための記事です。nodeもexpressも初心者のiOSエンジニアが書いてますので、同じような状況の方にとって有益かと思います。

以下で、app.jsを上から順に読んで、必要なリファレンスへのリンクを載せたり、ざっくりこういうことしてるってことを書きます。


app.js (express ver4.12.3の場合)

ソースはなにもしなければ、こんな感じになっていると思います。


app.js

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モジュールをインスタンス化


1~6行目

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タイプあるようです:



  1. Core Modules ... nodeのビルトインモジュール。nodeのソースのlib/配下にある。


  2. File Modules ... 1で見つからなければ、同名のファイル(.js/.json/.node)を読み込みます。


  3. 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の使い方

を参照するといいです。


ルーティングモジュールを読み込む


8-9行目

var routes = require('./routes/index');

var users = require('./routes/users');

このrequireは、上記タイプの2に相当します。'./' から始まる場合は、require() を呼んだファイルからの相対パスになります。では、index.jsの中を見てみましょう。


index.js


/routes/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などの処理を行っていきます。


11行目

var app = express();



app.set(name, value)

app.set(name, value)でアプリケーションの各種設定ができます。

'name'は、app setting tableで予め定義されているのでご注意ください。

ここでは、viewテンプレートが入ったフォルダと、viewテンプレートエンジンを設定しています。


14-15行目

// 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に配置したらコメントアウトをはずしてね、と丁寧にかいてあります。


17-18行目

// uncomment after placing your favicon in /public

//app.use(favicon(__dirname + '/public/favicon.ico'));


ログ出力方法を設定


19行目

app.use(logger('dev'));



パラメータのパース方法を設定


20行目

app.use(bodyParser.json());



パラメータのパースで利用するライブラリを設定


21行目

app.use(bodyParser.urlencoded({ extended: false }));


urlエンコードの際に、querystringライブラリを使うか、qsライブラリを使うかの設定。falseが前者。


クッキーのパーサーを設定


22行目

app.use(cookieParser());



静的ファイルが配置されているディレクトリを設定


23行目

app.use(express.static(path.join(__dirname, 'public')));


express.static(root, [options]) は、expressにビルトインの唯一のミドルウェアで、rootディレクトリ配下に配置している静的ファイルへのルーティングを自動で行ってくれます。

例えば、myapp/public/images/hoge.png/public/license.htmlといったものにアクセスすることができるようになります。


ルーティング設定を適用する


25-26行目

app.use('/', routes);

app.use('/users', users);

こういうふうに1階層まではapp.jsに書いて、残りは各ルーティングモジュールに任せるという設計かなと思います。


エラーハンドリング


HTTPステータス404エラー


28-33行目

// 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環境でのエラー出力処理


37-47行目

// 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環境でのエラー出力処理


51-57行目

// 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の詳細は吐出されません。


モジュールとして本アプリを出力


60行目

module.exports = app;


本app.js自体も、外部モジュールとして利用できるようになっている、ということでしょうか。。

最後歯切れわるかったですけど、こんな感じです。間違いや、こうだよということがあればご指摘お願いします!