Expressのルーティングは、HTTPメソッドとパスをハンドラー関数に結びつけます。app.get('/users', handler) は /users へのGETリクエストに答え、app.post()、app.put()、app.delete() はほかのメソッドで同じことをします。/users/:id のようにコロンで始まるセグメントはルートパラメーターで、その値は req.params に入ります。アプリが大きくなっても整理しておけるように、関連するルートは express.Router() にまとめ、app.use('/api', router) でプレフィックスの下にマウントします。
GET /users/2 は {"id":2,"name":"Linus"} を返します。ルートパラメーターは常に文字列なので、ハンドラーは Number(req.params.id) で比較しています。ボディを {"name": "Grace"} にして POST /users を送ると 201 が返ります。続けて DELETE /users/1 と GET /users を送ると、リストが変わるのがわかります。
プレフィックス付きのexpress.Router()
ルーターは、自分のルートとミドルウェアを持つ小さなアプリです。パスは相対的で、/api/books にマウントしたルーターの中で /:id と書いたルートは /api/books/:id に答えます。実際のプロジェクトでは、ルーターはそれぞれ自分のファイルに置き、最後に module.exports = router と書きます。
GET /api/books/1 は {"id":1,"title":"Dune"} を返します。router.route('/:id') はパスを一度だけ書き、メソッドごとのハンドラーをチェーンでつなぎます。PUT /api/books/1 を送ると {"updated":"1"} が返ります。マウントするパスを /v2/books に変えると、同じルーターがそこで答えるようになります。
ルートの順番:最初にマッチしたものが勝つ
Expressは登録した順にルートを試し、最初にレスポンスを送ったところで止まります。パラメーターのルートはその位置に来るものを何でも受け取るので、その後に登録した固定のパスには決して届きません。
GET /users/new は {"route":"/users/:id","id":"new"} を返します。パラメーターのルートが先に登録されていて、それを受け取ってしまったからです。/users/new のルートを /users/:id より上に移して実行し直すと、{"route":"/users/new"} が返ります。続けて GET /books を送ると、一番下の404ハンドラーに届きます。
404ハンドラー
Expressはマッチしないリクエストに、Cannot GET /books という独自のHTMLページで答えます。APIならたいていJSONを返したいので、上のブロックのように、パスなしの app.use() を最後のルートとして追加します。これは、それより前のすべてのルートがリクエストを次に回したときだけ実行されます。ハンドラーが投げたエラーは別の話で、引数が4つの専用ハンドラーを使います。Expressのエラー処理で扱っています。
Express 5のルートパス
Express 5ではパスの書き方が変わりましたが、ネット上のコード例の多くはExpress 4向けに書かれています。
| パターン | Express 4 | Express 5 |
|---|---|---|
| すべてのパス | app.get('*', fn) | app.get('/*splat', fn) |
| 省略可能なパラメーター | /users/:id? | /users{/:id} |
| パラメーターのパターン | /users/:id(\\d+) | 非対応。ハンドラーで値を確認する |
* だけ、あるいはパラメーターの後の ? は、ルートを登録した時点で TypeError を投げるので、アプリが起動しません。
よくある間違い
404ハンドラーを最初に登録する。 パスなしの app.use() はすべてにマッチするので、どのリクエストも404になります。一番下に置きましょう。
プレフィックスが取り除かれることを忘れる。 /api にマウントしたルーターの中では、req.path に /api は含まれません。フルパスが必要なら req.originalUrl を使います。
パラメーターを数値と比較する。 req.params.id は文字列の '1' なので、req.params.id === 1 は常にfalseです。
よくある質問
app.useとapp.getの違いは何ですか?
app.get('/users', fn) は、パスがちょうど /users のGETリクエストのときだけ fn を実行します。app.use('/users', fn) はすべてのメソッドで、/users で始まるどのパスでも実行されます。だからルーターやミドルウェアのマウントに使われます。
Expressのルートで大文字と小文字を区別するには?
ルートはデフォルトで大文字と小文字を区別しないので、/Users は /users にマッチします。app.set('case sensitive routing', true) でオフにするか、ルーター単位で express.Router({ caseSensitive: true }) を使います。
ネストしたルーターでreq.paramsが空になるのはなぜですか?
ルーターが見えるのは自分のパスのパラメーターだけです。/users/:userId/posts にマウントするなら、express.Router({ mergeParams: true }) で作れば、親パスの req.params.userId が使えるようになります。
すべてのルートを1つのファイルに書くべきですか?
ルートがいくつもあるなら、やめましょう。リソースごとにルーターを作り(routes/users.js、routes/orders.js)、module.exports = router でエクスポートして、メインのファイルで app.use() を使ってそれぞれマウントします。