Express入門 — ルーティング・ミドルウェア・エラーハンドリング
この章の目次開く
Node.js標準の http モジュールだけでもWebサーバーは作れます。しかし「URLごとの処理の振り分け」「リクエストボディのパース」「共通処理の差し込み」をすべて自前で書くことになり、コードはすぐに if 文の迷路になります。
Expressは、この定型部分を引き受けてくれる、Node.jsで最も広く使われてきたWebフレームワークです。この章では、Expressの3つの柱——ルーティング、ミドルウェア、エラーハンドリング——を学びます。
学習者今からWebフレームワークを学ぶなら、もっと新しいものじゃなくてExpressでいいんですか?
先生Expressの「ルーティング + ミドルウェア」という設計は、FastifyやHonoなど後発フレームワークにもほぼそのまま受け継がれているんだ。つまりExpressを理解すれば、他のフレームワークは「同じ概念の別記法」として読める。学ぶ価値は今でも十分あるよ。
最小のExpressサーバー
まずは動くものを見てみましょう。npmでインストールして、数行書くだけです。
npm install expressconst express = require('express');
const app = express(); // アプリケーション本体を作る
app.get('/', (req, res) => {
res.send('Hello Express!');
});
app.listen(3000, () => {
console.log('http://localhost:3000 で起動しました');
});node app.js で起動してブラウザで http://localhost:3000 を開くと、Hello Express! が表示されます。

ルーティング — URLとメソッドで処理を振り分ける
ルーティングとは「どのURL・どのHTTPメソッドに、どの処理を対応させるか」の定義です。
構文: app.METHOD(path, handler)(METHODは get / post / put / delete など)
| 引数 | 渡せるもの | 説明 |
|---|---|---|
path(第1引数) | 文字列 | URLのパス。:名前 でパスパラメータを定義できる |
handler(第2引数以降) | 関数 (req, res, next) | リクエスト処理。複数渡すと順番に実行される |
戻り値: app 自身(メソッドチェーンできる)
// GET /users — 一覧
app.get('/users', (req, res) => {
res.json(users);
});
// GET /users/42 — :id の部分が req.params.id に入る
app.get('/users/:id', (req, res) => {
const user = users.find((u) => u.id === Number(req.params.id));
if (!user) {
return res.status(404).json({ error: 'USER_NOT_FOUND' });
}
res.json(user);
});
// POST /users — ボディの値で作成
app.post('/users', (req, res) => {
const created = createUser(req.body);
res.status(201).json(created);
});リクエストから値を取り出す3つの場所
| 取り出し元 | プロパティ | 例 |
|---|---|---|
| パスパラメータ | req.params | /users/:id + GET /users/42 → { id: '42' } |
| クエリ文字列 | req.query | GET /users?page=2 → { page: '2' } |
| リクエストボディ | req.body | POST のJSONボディ(express.json() が必要、後述) |
req.params と req.query の値はすべて文字列です。数値として使うなら Number() での変換と検証を忘れないでください。
レスポンスを返す
res の代表的なメソッドです。
| メソッド | 説明 |
|---|---|
res.json(obj) | オブジェクトをJSONで返す(Content-Typeも自動設定)。APIの基本形 |
res.status(code) | ステータスコードを設定する。res.status(404).json(...) のようにチェーンする |
res.send(data) | 文字列・HTML・Bufferなどを返す |
res.redirect(url) | リダイレクトする |
ミドルウェア — Expressの本体
Expressの理解の核心はミドルウェアです。ミドルウェアとは、リクエストがルートハンドラに届く前後に差し込まれる関数のことで、Expressアプリの実体は「ミドルウェアが並んだパイプライン」です。
構文: ミドルウェアは (req, res, next) の3引数の関数
| 引数 | 説明 |
|---|---|
req | リクエストオブジェクト。プロパティを追加して後続へ情報を渡せる |
res | レスポンスオブジェクト。ここでレスポンスを返して処理を打ち切ることもできる |
next | 次のミドルウェアへ進む関数。呼ばないとリクエストが止まったままになる |
app.use() で登録すると、すべてのリクエストに適用されます。
// 自作ミドルウェアの例: 全リクエストをログに残す
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // これを呼ぶと次の処理へ進む
});
// 組み込みミドルウェア: JSONボディをパースして req.body に入れる
app.use(express.json());
// このルートに届く時点で、ログ記録とボディのパースが済んでいる
app.post('/users', (req, res) => {
res.status(201).json(createUser(req.body));
});ミドルウェアの形をしているものは自作に限りません。express.json()(ボディのパース)や express.static()(静的ファイル配信)のような組み込みのほか、CORS対応やセキュリティヘッダー付与などもnpmのミドルウェアとして提供されており、app.use() で差し込むだけで機能が増えていきます。
学習者next() を呼ぶのを忘れるとどうなるんですか?エラーになる?
先生エラーにすらならないのが厄介なところ。リクエストはそのミドルウェアで止まったまま、クライアントは応答が返るのを永遠に待ち続けることになる。「特定のAPIだけタイムアウトする」という症状を見たら、next()の呼び忘れを疑うといいよ。
エラーハンドリング — 4引数の特別なミドルウェア
Expressでは、引数を4つ持つミドルウェアだけが「エラーハンドラ」として扱われるという規約があります。ルートハンドラでthrowされたエラーや next(err) で渡されたエラーは、通常のミドルウェアを飛ばしてエラーハンドラに直行します。
// エラーハンドラ: 引数が (err, req, res, next) の4つであることが目印
// すべてのルート定義より「後」に登録する
app.use((err, req, res, next) => {
console.error(err.stack); // 詳細はログへ
// ユーザーには整理された情報だけを返す
res.status(500).json({
error: { code: 'INTERNAL_ERROR', message: 'サーバーエラーが発生しました' },
});
});エラーハンドラを1か所に置くことで、各ルートに try...catch を散らばらせず、「予期するエラーは4xx、予期しないエラーは500 + ログ」というエラーハンドリングの章で学んだ方針をアプリ全体で統一できます。
非同期ハンドラのエラーに注意
ルートハンドラを async 関数にした場合の失敗の扱いは、Expressのバージョンで異なります。
app.get('/users/:id', async (req, res) => {
const user = await db.findUser(req.params.id); // これが失敗すると?
res.json(user);
});- Express 5 — async関数の拒否(rejection)は自動的にエラーハンドラへ渡されます
- Express 4 — 自動では渡されません。
try...catchしてnext(err)を呼ぶか、ラッパー関数を挟む必要があります
// Express 4での定番パターン: catchしてnextに渡すラッパー
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await db.findUser(req.params.id);
res.json(user);
}));404はどう扱うか
どのルートにも一致しなかったリクエストは、ルート定義の最後に置いた「全部に一致するミドルウェア」で受けます。
// すべてのルート定義の後、エラーハンドラの前に置く
app.use((req, res) => {
res.status(404).json({ error: { code: 'NOT_FOUND', message: '存在しないURLです' } });
});ミドルウェアは順番に評価されるので、「最後まで誰も応答しなかった = 404」という構造がそのまま表現できるわけです。
よくあるハマりどころ
req.body が undefined
express.json() を登録していないか、ルート定義より後に登録しているのが原因です。ボディを使うすべてのルートより前に app.use(express.json()) を置きます。
「Cannot set headers after they are sent」エラー
1つのリクエストに2回レスポンスを返そうとしたときのエラーです。典型例は、エラー分岐で res.status(404).json(...) した後に return を忘れ、続く res.json(...) も実行されるパターンです。レスポンスを返す行は return とセットを習慣にしましょう。
ミドルウェアの順番違いで認証がすり抜ける
認証ミドルウェアより前に定義されたルートは、認証なしでアクセスできます。「共通で守るものは先に app.use、公開ルートだけ認証の前に定義」のように、順番を意識して構成します。
この章のまとめ
- Expressは「ルーティング + ミドルウェア」の薄いフレームワーク。この設計は後発フレームワークにも共通する
- ルーティングは
app.METHOD(path, handler)。入力はreq.params/req.query/req.bodyの3か所から取り出す(値は文字列) - Expressアプリの実体は、登録順に実行されるミドルウェアのパイプライン——「順番」が設計の中心になる
- エラー処理は4引数のエラーハンドラに集約する。asyncハンドラの失敗の扱いはExpress 4と5で違う点に注意
- 404は「最後まで一致しなかったら」のミドルウェアで表現する
次章では、Expressアプリの先にあるデータベース接続と、環境変数・秘密情報・CORSといった実務のセキュリティ注意点を学びます。
参考リンク
- Express 公式サイト(日本語) — インストールから各ガイドへの入り口
- Express: ルーティング — ルート定義とパスパラメータの公式ガイド
- Express: ミドルウェアの使用 — ミドルウェアの種類と登録方法
- Express: エラー処理 — エラーハンドラと非同期エラーの公式ガイド
