LoginSignup
511

More than 5 years have passed since last update.

逆引きメモ:expressの使い方

Last updated at Posted at 2014-02-21

expressの使い方を逆引き形式でまとめてみた。参考にしたのは2014年2月20日時点(express 3.4.8時点)の公式ドキュメントおよびソースコード、サンプルなど。

ルーティングを設定する

  • ルーティングを設定するには(i.e. パスとコントローラ関数をマッピングするには)app.get(path, func)app.post(path, func)など各HTTP Verbに対応した関数を使う
  • ルーティング設定は書かれた順番に評価され、リクエストとpathの一致したものが適用される
  • pathは文字列または正規表現を指定可能
  • funcのシグネチャはfunction(req, res[, next])
  • nextはコールバック関数
    • 引数無しnext()で呼び出すと、次に一致するルーティング設定に処理が移る
    • 引数有りnext(object)で呼び出すと、エラーハンドリング用の関数に処理が移る(エラーハンドリングについては別項目参照のこと)
app.get('path_to_somewhere', function(req, res) {
    :
  // something
    :
});

app.post(/^\/items\/[0-9]+\.html$/, function(req, res) {
    :
  // something
    :
});

/*
  app.get()、app.post()以外のルーティング設定関数は以下の通り。それぞれ同名のHTTP Verbに対応している。

  app.put()
  app.head()
  app.delete()
  app.options()
  app.trace()
  app.copy()
  app.lock()
  app.mkcol()
  app.move()
  app.propfind()
  app.proppatch()
  app.unlock()
  app.report()
  app.mkactivity()
  app.checkout()
  app.merge()
  app.m-search()
  app.notify()
  app.subscribe()
  app.unsubscribe()
  app.patch()
  app.search()
*/

テンプレートをレンダリングする

  • テンプレートをレンダリングするにはresponse.render(templateName)を使う
  • templateNameはテンプレートファイルの名称であり、app.set('views', path)で指定したパス直下に置く必要がある
  • templateNameでは拡張子を省略可能。省略した場合app.set('view engine', ext)で指定したデフォルト拡張子であると解釈される
  • レンダリングに利用するテンプレートエンジンはapp.engine(ext, engineFunc)で登録する
// テンプレートファイルを置くパスを設定。デフォルトは "process.cwd() + '/views'"
app.set('views', 'path_to_somewhere');

// レンダリングに利用するテンプレートエンジンを登録
var jadeEngine = require('jade').someFunc;
app.engine('jade', jadeEngine);

var ejsEngine = require('ejs').someFunc;
app.engine('ejs', ejsEngine);
/*
app.engine()の第二引数はfunction(path, options, callback)のシグネチャを持つ必要がある。consolidate.js(https://github.com/visionmedia/consolidate.js)を使うと様々なテンプレートエンジンのシグネチャをこの形式に合わせてくれるので便利。
*/

// templateNameで拡張子が省略された場合のデフォルト拡張子
app.set('view engine', 'ejs');


//コントローラーの処理
app.get('path_to_somewhere', function(req, res) {
    :
  // レンダリング実行
  res.render('index');  //この例の場合index.ejsファイルとして解釈される
    :
});

テンプレートに値を埋め込む

  • テンプレートに値を埋め込む方法は4つ
    1. response.render(templateName, object)objectのプロパティで指定
    2. response.locals.name = valueで指定
    3. app.locals.name = valueで指定
    4. app.set(name, value)で指定
  • 1〜3の方法で埋め込んだ値は、テンプレートからはグローバルオブジェクトのプロパティとして参照可能
  • 4の方法で埋め込んだ値は、テンプレートからはsettingsオブジェクトのプロパティとして参照可能
app.get('path_to_somewhere', function(req, res) {
    :
  // 方法1
  res.render('index', {title1: 'exposed via res.render'});
    :
});

app.get('path_to_somewhere', function(req, res) {
    :
  // 方法2
  res.locals.title2 = 'exposed via res.local');
  res.render('index');
    :
});

// 方法3
app.locals.title3 = 'exposed via app.local');

// 方法4
app.set('title3', 'exposed via app.set');
テンプレート側(ejsの例)
<h1> <%= test1 %> </h1>  <!-- グローバルオブジェクトのプロパティとして参照可能 -->
<h1> <%= test2 %> </h1>  <!-- グローバルオブジェクトのプロパティとして参照可能 -->
<h1> <%= test3 %> </h1>  <!-- グローバルオブジェクトのプロパティとして参照可能 -->
<h1> <%= settings.test3 %> </h1>  <!-- settingsオブジェクトのプロパティとして参照可能 -->

<!-- 以下のように出力される
<h1>exposed via res.render</h1>
<h1>exposed via res.local</h1>
<h1>exposed via app.local</h1>
<h1>exposed via app.set</h1>
-->

レスポンスのヘッダーを指定する

  • レスポンスのヘッダーを指定するにはresponse.set(name, value)を使う
  • 複数のヘッダーをまとめて指定する場合はresponse.setの引数にオブジェクトを渡す
  • response.setの別名としてresponse.headerを使っても良い(名前が違うだけで機能は全く同じ)
app.get('path_to_somewhere', function(req, res) {
    :
  res.set('Cache-Control': 'max-age=600');
  res.set('ETag': '12345');
    :
});

app.get('path_to_somewhere', function(req, res) {
    :
  // オブジェクトを引数に渡して複数同時に指定
  res.set({
    'Cache-Control': 'max-age=600',
    'ETag': '12345'
  });
    :
});

レスポンスのステータスコードを指定する

  • レスポンスのステータスコードを指定するにはresponse.status(code)を使う
app.get('path_to_somewhere', function(req, res) {
    :
  res.status(501);
    :
});

レスポンスにCookieを含める

  • レスポンスにCookieを含めるにはresponse.cookie(name, value[, option])を使う
  • valueには値だけでなくオブジェクトも指定可能(JSON化してシリアライズされる)
  • optionには以下の値を指定できる
    • domain: Cookieを有効とするドメイン(初期値:ホストのドメイン)
    • path: Cookieを有効とするパス(初期値:'/')
    • httpOnly: HTTPだけで有効にするか(初期値:false)
    • maxAge: 破棄されるまでの時間(ミリ秒)。このオプションはexpire属性のシンタックスシュガー(初期値:session)
    • secure: Cookieに署名するか(初期値:false)
  • secureオプションをtrueにする場合は、cookieParserミドルウエアを読み込んで、署名用の鍵を指定する必要がある(cookieParserミドルウエアの読み込みは別項目参照のこと)
app.get('path_to_somewhere', function(req, res) {
    :
  res.cookie('cookie_name', 'cookie_value', {maxAge: 900000, httpOnly: true});
    :
});

レスポンスからCookieを取り除く

  • レスポンスからCookieを取り除くにはresponse.clearCookie(name)を使う
app.get('path_to_somewhere', function(req, res) {
    :
  res.clearCookie('cookie_name');
    :
});

リダイレクトする

  • リダイレクトするにはresponse.redirect([status, ] url)を使う
  • statusを省略した場合は302(Found)が使われる
app.get('path_to_somewhere', function(req, res) {
    :
  res.redirect(302, "../login");
    :
});

ミドルウエアを使う

  • ミドルウエアとはexpressにプラグインして使う機能のことで、connectフレームワークを基盤としている
  • expressに同梱されているミドルウエアはexpressのドキュメント、またはconnectのドキュメントを参照のこと
  • 利用するミドルウエアはapp.use([path, ] middlewareFunc)で読み込む
  • pathを指定した場合、リクエストとpathが一致した場合のみそのミドルウエアが利用される。pathが省略された場合は'/'として扱われる
  • ミドルウエアの実態はmiddlewareFunc関数で、そのシグネチャはfunction(req, res, next)
  • ミドルウエアはapp.useされた順番に適用される(ルーティング設定の適用順序と同じ考え方)
app.use(middlewareFactory(someOption));

/*
ミドルウエアはファクトリ関数(関数をreturnする関数)を使って生成するものが多い。この例の場合、middlewareFactory関数はfunction(req, res, next)というシグネチャを持った関数を返す
*/

staticファイルを公開する

  • 画像ファイルやCSSファイル、JavaScriptファイルなどのstaticファイルを公開するには、staticミドルウエアを利用する
  • staticミドルウエアのファクトリ関数はexpress.static(path[, option])
  • pathには、staticファイルを公開したいフォルダ / ファイルへのパスを指定
  • optionには以下の値を指定できる
    • maxAge: Browser cache maxAge in milliseconds. defaults to 0
    • hidden: Allow transfer of hidden files. defaults to false
    • redirect: Redirect to trailing "/" when the pathname is a dir. defaults to true
    • index: Default file name, defaults to 'index.html'
app.use(express.static('path_to_somewhere', {hidden: true}));

リクエストに含まれるCookieを読み出す

  • リクエストに含まれるCookieを読み出すには、cookieParserミドルウエアを使う(このミドルウエアは将来的にdeprecatedになると予告されており、cookiesミドルウエアに置き換わる予定)
  • cookieParserを使うと、リクエストに含まれるCookieの情報がrequest.cookies[]メンバに格納される
  • cookieParserミドルウエアのファクトリ関数はexpress.cookieParser([secret])
  • secretには署名付きCookie用の鍵を指定する
app.use(express.cookieParser('some_secret'));

セッションを使う

  • セッション機能を使うには、sessionミドルウエアを利用する
  • cookieを利用してセッションIDを管理するのでcookieParserミドルウエアも必須
  • sessionミドルウエアを使うと、セッション情報を保持するオブジェクトrequest.sessionが生成される
  • request.sessionオブジェクトにはセッションをリセットしたり破棄するための関数が用意されている。詳細はconnectのドキュメントを参照のこと
  • sessionミドルウエアのファクトリ関数はexpress.session([option])
  • optionには以下の値を指定できる
    • key: cookie name defaulting to connect.sid
    • store: session store instance
    • secret: session cookie is signed with this secret to prevent tampering
    • cookie: session cookie settings, defaulting to { path: '/', httpOnly: true, maxAge: null }
    • proxy: trust the reverse proxy when setting secure cookies (via "x-forwarded-proto")
// ミドルウエアの読み込み
app.use(express.cookieParser());
app.use(express.session({secret: 'secret_for_sign'}));

// request.sessionオブジェクトを利用する(この例では訪問回数を記録している)
app.get('path_to_somewhere', function(req, res) {
  if (req.session.counter) {
    res.end('you already visit this page ' + req.session.counter + '-th time');
    req.session.counter++;
  }
  else {
    req.session.counter = 1;
    res.end('this is first time you visit this page!');
  }
});

エラーを発生させる

  • コントローラー関数の中でエラーを発生させるには、例外をthrowする、またはnext()コールバックの引数に例外オブジェクトを渡す
// 例外をthrowする
app.get('path_to_somewhere', function(req, res) {
    :
  throw new Error('this is error');
});

// next()コールバックを呼び出す
app.get('path_to_somewhere', function(req, res) {
    :
  next(new Error('this is error');
});

エラーをハンドリングする

  • エラーをハンドリングするには、app.use()を使ってエラーハンドリング用関数を指定する(つまりエラーハンドリングもミドルウエアと同じ扱い)
  • エラーハンドリング用関数のシグネチャはfunction(err, req, res, next)でなければならない(その他ミドルウエアとはシグネチャが異なる。ミドルウエアにはerrが含まれない)
  • エラーハンドリング用関数の中でnext()を引数なしで呼び出した場合は、その他のミドルウエアに処理が移る
  • next()をerr引数付きで呼び出した場合は、その他のエラーハンドリング用関数だけが実行される(その他のミドルウエアは実行されない)
app.use(function(err, req, res, next) {
    :
  next(err);  // もう一つのエラーハンドリングコールバックに処理を渡す
});

app.use(function(err, req, res, next) {
    :
  res.status(500);
  res.render('500');
});

CSRF対策する

  • csrfミドルウエアを使って以下を行う
    • トークンを生成してセッションに保存(csrfミドルウエアが自動で行う)
    • Formをレンダリングする際にトークンを埋め込む(要実装)
    • FormからのPOSTに含まれるトークンとセッションに保存されたトークンを比較し、異なっている場合はエラーを発生させる(csrfミドルウエアが自動で行う)
    • エラーを適切にハンドリングする(要実装、詳細は別項目参照のこと)
  • セッションを利用するのでcookieParserミドルウエアとsessionミドルウエアも必須
  • csrfミドルウエアのファクトリ関数はexpress.csrf()
// sessionミドルウエアの読み込み
app.use(express.cookieParser());
app.use(express.session({secret: 'secret_to_sign'}));

// csrfミドルウエアの読み込み
app.use(express.csrf());

// エラーハンドリングの実装
app.use(function(err, req, res, next) {
    :
  // something
    :
});

// ルーティング設定
app.get('path_to_form', function(req, res, next) {
    :
  // セッションに保存されたtokenをFormに埋め込む
  res.local.token = req.session._csrf;
  res.render('index');
});
View側(ejsの例)
<form action='/form', method>
  <input type='hidden' name='_csrf' value='<%=token%>'/>
</form>

アクセスログ / エラーログを残す

  • アクセスログやエラーログを残すには、loggerミドルウエアを利用する
  • loggerミドルウエアのファクトリ関数はexpress.logger([string | function | option])
  • 引数で出力フォーマットや出力先を指定できる。詳細はloggerミドルウエアのマニュアルを参照のこと
  • 引数を省略した場合は、Apacheと同等の出力フォーマットでstdoutに出力される
app.use(express.logger());

ルーティングとミドルウエアの実行順序を明示する

  • 原則として、ミドルウエアやルーティング設定はソースコードに書かれた順番に実行される
  • ただし、ルーティング設定(app.get()app.post())は、express内部で一つにまとめられ、一つのミドルウエアのように扱われる(ここではルーティングミドルウエアと呼ぶ)
  • このルーティングミドルウエアが実行されるのは、最初のルーティング設定が書かれた箇所となる(以下の例参照)
// ①〜⑥の順序で実行される

// 最初のルーティング設定
// この位置にルーティングミドルウエアが入る
app.get('path_to_somewhere', function(req, res) { // ①
    :
});

app.get('path_to_somewhere', function(req, res) { // ②
    :
});

app.use(someMiddleware); // ⑤
app.use(otherMiddleware); // ⑥

app.get('path_to_somewhere', function(req, res) { // ③
    :
});

app.get('path_to_somewhere', function(req, res) { // ④
    :
});
  • このままではソースコード全体を見なければ実行順序が分からない(特に大規模なシステムの場合)
  • そこで、実行順序を見通し良く記述するためにapp.use(app.router)を使うとよい
  • app.use(app.router)はルーティングミドルウエアそのものであり、これが書かれた位置にルーティングミドルウエアが入ることになる
// ①〜⑥の順序で適用される

app.get('path_to_somewhere', function(req, res) { // ③
    :
});

app.get('path_to_somewhere', function(req, res) { // ④
    :
});

//
// 全体を見なくてもここだけ見れば実行順序が判る
//
app.use(someMiddleware); // ①
app.use(otherMiddleware); // ②
app.use(app.router); // この位置にルーティングミドルウエアが入る

app.get('path_to_somewhere', function(req, res) { // ⑤
    :
});

app.get('path_to_somewhere', function(req, res) { // ⑥
    :
});

全てのHTTP Verbに一致するルーティングを指定する

  • HTTP Verb毎のルーティング設定は前述の通りだが、全てのVerbに対して適用するルーティング設定はapp.all()で行う
app.all('path_to_somewhere', function(req, res, next) {
    :
});

コンテンツをgzip / deflatで圧縮して返す

  • compressミドルウエアを利用する
app.use(express.compress());

ディレクトリをリスティングする

  • ディレクトリをリスティングするには、directoryミドルウエア利用する
  • direcotoryミドルウエアのファクトリ関数はexpress.directory(path [,option])
  • pathには、directoryを公開したいフォルダへのパスを指定する
  • optionには以下の値を指定できる
    • hidden: display hidden (dot) files. Defaults to false.
    • icons: display icons. Defaults to false.
    • filter: Apply this filter function to files. Defaults to false.
app.use(express.directory('path_to_somewhere'), {hidden: false});

faviconを指定する

  • faviconがstaticファイルとして公開されていれば何もしなくてもよい
  • ただし、faviconへのアクセスをログに残したくない場合は、faviconミドルウエアを利用して以下のようにするとよい
// 先にfaviconミドルウエアを適用
app.use(express.favicon('path_to_favicon'));

// その後loggerミドルウエアを適用
app.use(express.logger());

デバッグ関連

ルーティング設定を確認する

  • ルーティングの設定はapp.routesオブジェクトに格納されている
  • このオブジェクトをconsole.logに出力すれば、登録されたルーティング設定を確認できる
console.log(app.routes);

/* 出力の例
{ get: 
   [ { path: '/',
       method: 'get',
       callbacks: [Object],
       keys: [],
       regexp: /^\/\/?$/i },
   { path: '/user/:id',
       method: 'get',
       callbacks: [Object],
       keys: [{ name: 'id', optional: false }],
       regexp: /^\/user\/(?:([^\/]+?))\/?$/i } ],
delete: 
   [ { path: '/user/:id',
       method: 'delete',
       callbacks: [Object],
       keys: [Object],
       regexp: /^\/user\/(?:([^\/]+?))\/?$/i } ] }
*/

express内部のデバッグログを出力する

  • DEBUG環境変数にexpress:*を設定した上でnodeを起動すれば、デバッグログが出力される
$ DEBUG=express:* node app.js

# 出力の例
# express:application booting in development mode +0ms
# express:router defined get /hello.txt +0ms
# express:router defined get /hello.txt +1ms

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
511