逆引きメモ：expressの使い方


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

- [公式ドキュメント](http://expressjs.com/api.html)
- [ソースコード](https://github.com/visionmedia/express)
- [サンプル](https://github.com/visionmedia/express/tree/master/examples)

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

```javascript
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)```で登録する

```javascript
// テンプレートファイルを置くパスを設定。デフォルトは "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```のプロパティで指定
    1. ```response.locals.name = value```で指定
    1. ```app.locals.name = value```で指定
    1. ```app.set(name, value)```で指定
- 1〜3の方法で埋め込んだ値は、テンプレートからはグローバルオブジェクトのプロパティとして参照可能
- 4の方法で埋め込んだ値は、テンプレートからは```settings```オブジェクトのプロパティとして参照可能

```javascript
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');
```

```html:テンプレート側（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```を使っても良い（名前が違うだけで機能は全く同じ）

```javascript
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)```を使う

```javascript
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ミドルウエアの読み込みは[別項目](#2-11)参照のこと）

```javascript
app.get('path_to_somewhere', function(req, res) {
    :
  res.cookie('cookie_name', 'cookie_value', {maxAge: 900000, httpOnly: true});
    :
});
```


## レスポンスからCookieを取り除く
- レスポンスからCookieを取り除くには```response.clearCookie(name)```を使う

```javascript
app.get('path_to_somewhere', function(req, res) {
    :
  res.clearCookie('cookie_name');
    :
});
```


## リダイレクトする
- リダイレクトするには```response.redirect([status, ] url)```を使う
- ```status```を省略した場合は302（Found）が使われる

```javascript
app.get('path_to_somewhere', function(req, res) {
    :
  res.redirect(302, "../login");
    :
});
```


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

```javascript
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'

```javascript
app.use(express.static('path_to_somewhere', {hidden: true}));
```

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

```javascript
app.use(express.cookieParser('some_secret'));
```

## セッションを使う
- セッション機能を使うには、sessionミドルウエアを利用する
- cookieを利用してセッションIDを管理するのでcookieParserミドルウエアも必須
- sessionミドルウエアを使うと、セッション情報を保持するオブジェクト```request.session```が生成される
- ```request.session```オブジェクトにはセッションをリセットしたり破棄するための関数が用意されている。詳細は[connectのドキュメント](http://www.senchalabs.org/connect/session.html)を参照のこと
- 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")

```javascript
// ミドルウエアの読み込み
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()```コールバックの引数に例外オブジェクトを渡す

```javascript
// 例外を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引数付きで呼び出した場合は、その他のエラーハンドリング用関数だけが実行される（その他のミドルウエアは実行されない）

```javascript
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ミドルウエアが自動で行う）
    - エラーを適切にハンドリングする（要実装、詳細は[別項目](#2-14)参照のこと）
- セッションを利用するのでcookieParserミドルウエアとsessionミドルウエアも必須
- csrfミドルウエアのファクトリ関数は```express.csrf()```

```javascript
// 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');
});
```

```html:View側（ejsの例）
<form action='/form', method>
  <input type='hidden' name='_csrf' value='<%=token%>'/>
</form>
```


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

```javascript
app.use(express.logger());
```

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

```javascript
// ①〜⑥の順序で実行される

// 最初のルーティング設定
// この位置にルーティングミドルウエアが入る
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)```はルーティングミドルウエアそのものであり、これが書かれた位置にルーティングミドルウエアが入ることになる

```javascript
// ①〜⑥の順序で適用される

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毎のルーティング設定は[前述](#2-1)の通りだが、全てのVerbに対して適用するルーティング設定は```app.all()```で行う

```javascript
app.all('path_to_somewhere', function(req, res, next) {
    :
});
```


## コンテンツをgzip / deflatで圧縮して返す
- compressミドルウエアを利用する

```javascript
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.

```javascript
app.use(express.directory('path_to_somewhere'), {hidden: false});
```


## faviconを指定する
- faviconが[staticファイルとして公開](#2-10)されていれば何もしなくてもよい
- ただし、faviconへのアクセスをログに残したくない場合は、faviconミドルウエアを利用して以下のようにするとよい

```javascript
// 先にfaviconミドルウエアを適用
app.use(express.favicon('path_to_favicon'));

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


## デバッグ関連
### ルーティング設定を確認する
- ルーティングの設定は```app.routes```オブジェクトに格納されている
- このオブジェクトを```console.log```に出力すれば、登録されたルーティング設定を確認できる

```javascript
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を起動すれば、デバッグログが出力される

```bash
$ 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
```