エクスプレス・ルーティングとミドルウェア
チャーリーのAPIプロジェクトは当初、ルートが5つしかなく、すべてapp.jsで記述されていたため、まだかなり分かりやすかった。2か月後、ルートの数は30に増え、ファイルは500行を超え、APIエンドポイントに1つの変更を加えるだけでも途方もなく時間がかかるようになってしまった。そこでチームは、express.Router() を使用してルートをモジュールに分割することを決定しました。ユーザー関連のルートは users.js に、認証関連のルートは auth.js に配置し、カスタムロギングミドルウェアは別のファイルに移しました。分割後、各ファイルは単一の責任を持つようになり、メンテナンスの効率が大幅に向上しました。
学習内容:
express.Router()を使用してモジュール式ルートを分割する- ルートパラメータ、クエリ文字列、リクエスト本文の読み取り方法
- カスタムミドルウェアの作成と
next()メカニズム - サードパーティ製ミドルウェアの統合(CORS / Morgan / Helmet)
- エラー処理ミドルウェアの設計とグローバルなエラー処理
1. express.Router() モジュール型ルーティング
ルート数が増えるにつれて、すべてのルートを app.js に格納すると、ファイルが肥大化し、共同作業が困難になる可能性があります。express.Router() を使用すると、独立したルートインスタンスを作成し、それらをアプリケーション内の特定のパスプレフィックスにマウントすることができます。
| 機能 | アプリ内で直接ルートを定義 | Routerモジュールによるモジュール式ルーティング |
|---|---|---|
| ファイルの構成 | すべてを app.js にまとめる | モジュールごとに別々のファイルに分ける |
| パスプレフィックス | 各ルートのフルパスを指定 | マウント時に統一されたプレフィックスを設定 |
| チームでの共同作業 | 複数のユーザーによる同一ファイルの編集 | 各モジュールの独立したメンテナンス |
| 再利用性 | 低い | 高い;プロジェクト間で再利用可能 |
▶ 例:スタンドアロン型ユーザールーティングモジュールの作成
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.json({ users: ['Alice', 'Bob'] });
});
router.get('/:id', (req, res) => {
res.json({ user: req.params.id });
});
module.exports = router;
▶ 例:app.js へのルートモジュールのマウント
// app.js
const express = require('express');
const app = express();
const userRouter = require('./routes/users');
app.use('/users', userRouter);
app.listen(3000, () => {
console.log('Server running on port 3000');
});
/users を訪問すると router.get('/') に一致し、/users/42 を訪問すると router.get('/:id') に一致します。
2. ルートパラメータ、クエリ文字列、およびリクエスト本文
Expressでは、クライアントデータを受け取る方法を3つ用意しており、それぞれ異なるシナリオに適しています。
| データソース | アクセス方法 | URLの例 | 代表的な用途 |
|---|---|---|---|
| ルーティングパラメータ | req.params |
/users/42 |
特定のリソースを識別する |
| クエリ文字列 | req.query |
/users?role=admin |
フィルタ、並べ替え、検索 |
| リクエスト本文 | req.body |
POST本文 | フォーム/JSONデータの送信 |
▶ 例:ルートパラメータ :id
app.get('/products/:id', (req, res) => {
const productId = req.params.id;
res.json({ productId });
});
▶ 例:クエリ文字列 req.query
app.get('/products', (req, res) => {
const { category, sort, page } = req.query;
res.json({ category, sort, page: page || 1 });
});
/products?category=electronics&sort=price&page=2、req.query、および{ category: 'electronics', sort: 'price', page: '2' }にアクセスしてください。
▶ 例:リクエスト本文 req.body および express.json()
app.use(express.json());
app.post('/products', (req, res) => {
const { name, price } = req.body;
res.status(201).json({ name, price });
});
注:
req.queryおよびreq.paramsの値はいずれも文字列であるため、手動で数値に変換する必要があります。
3. ミドルウェアの基礎と next() メカニズム
ミドルウェアは Express の核心となる概念です。すべてのリクエストは一連のミドルウェアを通過し、各ミドルウェア関数はリクエストを読み取ったり、レスポンスを変更したり、次のミドルウェア関数に制御を委譲したりすることができます。
graph LR A[Request] --> B[morgan Log] B --> C[express.json Analysis] C --> D[Custom auth Middleware] D --> E[Route Handling Functions] E --> F[Response] D -->|next error| G[Error-handling middleware]
▶ 例:最もシンプルなカスタムミドルウェア
function logger(req, res, next) {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next();
}
app.use(logger);
| ルール | 説明 |
|---|---|
next() を呼び出す必要があります |
そうしないと、リクエストは中断され、クライアントはレスポンスを受け取ることができません |
| 呼び出し順序は実行順序である | 最初に登録されたミドルウェアが先に実行される |
app.use() 全体に適用 |
app.use('/api', ...) /api パスにのみ適用 |
next('route') |
現在のルートにおける残りのミドルウェアをスキップする |
4. サードパーティ製ミドルウェアとの連携
このコミュニティでは、インストール後にたった1行のコードで有効化できる、すぐに使える幅広いミドルウェアを提供しています。
| ミドルウェア | 用途 | インストールコマンド |
|---|---|---|
| cors | クロスオリジンリクエストの問題の解決 | npm install cors |
| morgan | HTTPリクエストのログ記録 | npm install morgan |
| ヘルメット | 安全対応用ヘルメットの設置 | npm install helmet |
| express-rate-limit | リクエストレート制限 | npm install express-rate-limit |
| cookie-parser | クッキーの解析 | npm install cookie-parser |
▶ 例:CORS、Morgan、Helmet の統合
const cors = require('cors');
const morgan = require('morgan');
const helmet = require('helmet');
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
注: セキュリティ関連のミドルウェア(Helmet)を最初に配置し、その直後にロギング用ミドルウェアを配置することを推奨します。
5. エラー処理ミドルウェア
Expressコンベンション:4つの引数を持つ関数 (err, req, res, next) は、エラー処理用ミドルウェアです。いずれかのミドルウェアが next(err) を呼び出すと、その後の通常のミドルウェアをスキップして、直接エラー処理用ミドルウェアに進みます。
| 比較項目 | 標準ミドルウェア | エラー処理ミドルウェア |
|---|---|---|
| パラメータ数 | 3 (req, res, next) | 4 (err, req, res, next) |
| トリガー方式 | リクエストの到着順に実行 | next(err) トリガー |
| 登録場所 | 任意の場所 | 最後に配置する必要があります |
| 複数あることはありますか? | はい | はい、順番に実行されます |
▶ 例:グローバルエラー処理ミドルウェア
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: err.message || 'Internal Server Error'
});
});
▶ 例:ルートでエラーを発生させる
app.get('/admin', (req, res, next) => {
const err = new Error('Access denied');
err.status = 403;
next(err);
});
6. Mermaid:ルーターのモジュール式ルーティング構造
graph TD APP[app.js] --> UR["/users → userRouter"] APP --> AR["/auth → authRouter"] APP --> MW["middleware/logger.js"] UR --> U1["GET / → User List"] UR --> U2["GET /:id → User Details"] UR --> U3["POST / → Create a User"] AR --> A1["POST /login → Log In"] AR --> A2["POST /register → Register"] MW --> ML["Logging Middleware"]
7. 包括的な例:モジュール型ルーティング API
この例では、ユーザールーティング、認証ルーティング、およびロギングミドルウェアが別々のファイルに分割され、最終的にapp.jsに統合されています。
▶ 例:middleware/logger.js — カスタムロギングミドルウェア
function logger(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`);
});
next();
}
module.exports = logger;
▶ 例:routes/users.js — ユーザー・ルート・モジュール
const express = require('express');
const router = express.Router();
let users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
router.get('/', (req, res) => {
res.json(users);
});
router.get('/:id', (req, res, next) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
const err = new Error('User not found');
err.status = 404;
return next(err);
}
res.json(user);
});
router.post('/', (req, res) => {
const { name } = req.body;
if (!name) {
return res.status(400).json({ error: 'Name is required' });
}
const newUser = { id: users.length + 1, name };
users.push(newUser);
res.status(201).json(newUser);
});
module.exports = router;
▶ 例:routes/auth.js — 認証ルーティングモジュール
const express = require('express');
const router = express.Router();
router.post('/login', (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: 'Username and password required' });
}
res.json({ message: 'Login successful', token: 'mock-jwt-token' });
});
router.post('/register', (req, res) => {
const { username, email, password } = req.body;
if (!username || !email || !password) {
return res.status(400).json({ error: 'All fields are required' });
}
res.status(201).json({ message: 'Registration successful' });
});
module.exports = router;
▶ 例:app.js — 統合のエントリポイント
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const logger = require('./middleware/logger');
const userRouter = require('./routes/users');
const authRouter = require('./routes/auth');
const app = express();
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
app.use(logger);
app.use('/users', userRouter);
app.use('/auth', authRouter);
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: err.message || 'Internal Server Error'
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
テストを実行:
node app.js
Server running on port 3000
GET /users 200 3ms
GET /users/1 200 2ms
POST /auth/login 200 5ms
❓ よくある質問
next() が呼び出されなかった場合はどうなりますか?next() を呼び出すか、res.end() や res.json() などのメソッドを使用してレスポンスを直接終了させる必要があります。express.json()は、req.bodyを読み込む必要があるルートよりも先に登録する必要があります。そうしないと、req.bodyがundefinedになってしまいます。app.use()で登録します。next(err)を呼び出すミドルウェアはすべて、このハンドラーにルーティングされます。Access-Control-Allow-Originなどのフィールドを追加することで、指定されたオリジンからのフロントエンドアプリケーションがAPIにアクセスできるようにします。express.Router()とapp.get()を直接使用する場合の違いは何ですか?Routerは、任意のルートプレフィックスにマウントできる独立したルーターインスタンスを作成するため、モジュール化に適しています。一方、app.get()はアプリケーションインスタンスに直接登録されるため、シンプルなプロジェクトに適しています。機能的にはどちらも同等ですが、違いは構成方法にあります。req オブジェクトをどのように変更すれば、後続の処理にデータを渡すことができますか?req に、req.user = { id: 1 } などのプロパティを直接追加するだけです。そうすれば、後続のミドルウェアやルートは req.user を通じてこのデータにアクセスできるようになります。これは、Express におけるミドルウェア間の通信でよく使われるパターンです。📖 まとめ
- 1 express.Router():モジュール型ルーティングの基本概念と使い方
- 2 ルートパラメータ、クエリ文字列、リクエストボディの基本概念と使用方法
- 3 ミドルウェアの基礎概念と活用法、および next() メカニズム
- サードパーティ製ミドルウェア統合に関する4つの基本概念と活用方法
- エラー処理ミドルウェアの5つの基本概念と使用方法
- 6 マーメイド:ルーターモジュールにおけるモジュラー型ルーティング構造の基本概念と活用方法
- 7 詳細な例:モジュラー・ルーティングAPIの基本概念と使用方法
📝 練習問題
- このレッスンにあるすべてのコード例を完成させ、それぞれが正しく動作することを確認してください。
- 包括的な例を修正し、独自の拡張機能を追加する
- 公式ドキュメントを確認し、このレッスンで扱われていないAPIを1~2つ見つけ、それらのテストコードを作成してください。
- 振り返り:このレッスンで学んだことを、実際のプロジェクトにどのように活かしますか?
- このレッスンで学んだことと、これまでのレッスンの内容を組み合わせて、小さなプロジェクトを作成してみてください。



