Help us understand the problem. What is going on with this article?

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

More than 5 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自体も、外部モジュールとして利用できるようになっている、ということでしょうか。。

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

mito_log
ベトナム・ハノイ拠点の個人デベロッパ。東南アジア向けに素敵なサービスと文化を作るべく模索中。ベトナムで1年半農業。newbie skateboarder、ハノイITもくもく会毎週土曜朝9時頃 → https://bit.ly/2Oe9Ehk
http://mitolab.hatenablog.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした