Contents
カスタムミドルウェアの基礎と役割
Express.jsアプリケーションにおいてカスタムミドルウェアを実装するには、req(リクエスト)、res(レスポンス)、next(次のミドルウェアに処理を渡す関数)という3つのパラメータを持つ関数を作成することが基本です。この構造によって、リクエストがルートハンドラに到達する前後の処理や、認証・ログ記録などの共通ロジックを統一的に実装できます。
ミドルウェア関数の役割は「リクエストとレスポンスの間に挟まれて処理を行う仕組み」です。例えば、アクセスログを出力するミドルウェアではreq.methodやreq.urlから情報を取得し、next()で次の処理に移行します。一方で、リクエストが不正な場合などはres.send()やnext(err)を使って処理を終了させることも可能です。
ミドルウェア関数の基本構造と設計原則
ミドルウェア関数のテンプレートは以下の通りです:
|
1 2 3 4 5 6 7 8 9 10 11 |
function myMiddleware(req, res, next) { // reqにアクセスする処理(例: req.headers) console.log(`リクエストされたURL: ${req.url}`); // resを操作する処理(例: ヘッダの追加) res.setHeader('X-Test', 'MyMiddleware'); // 次のミドルウェアに処理を渡す next(); } |
この構造は、リクエストの前処理やルートハンドラの後処理など幅広い用途に応用できます。また、next()を呼ばずに処理を終了させることで、404エラーなどの即時レスポンスにも対応可能です。
重要ポイント: ミドルウェアの設計では「処理フローに与える影響」を常に意識し、
next()のタイミングやエラーハンドリングの仕組みを明確にしましょう。
ミドルウェアの適用範囲と設定方法
Express.jsでは、ミドルウェアを「アプリケーションレベル」または「ルーターレベル」で適用できます。この違いによって、処理の適用範囲が異なります。
アプリケーションレベルでの適用
app.use()メソッドは、全リクエストに対してミドルウェアを実行するグローバルな設定です。セキュリティやログ記録などの共通処理に適しています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const express = require('express'); const app = express(); // 全リクエストで実行されるミドルウェア app.use((req, res, next) => { console.log(`アクセスがありました: ${req.url}`); next(); }); app.get('/', (req, res) => { res.send('ホームページ'); }); |
このコードでは、/以外のルートにも自動的にログ記録が適用されます。アプリケーション全体に影響を与える処理には最適です。
ルーターレベルでの限定的な適用
ルーターインスタンス(express.Router())に対してrouter.use()を呼び出すことで、特定のルートのみにミドルウェアを適用できます。認証やリソース制限に利用されます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const express = require('express'); const router = express.Router(); // /admin以下のパスに限定して実行されるミドルウェア router.use((req, res, next) => { console.log(`管理画面へのアクセス: ${req.url}`); next(); }); router.get('/dashboard', (req, res) => { res.send('ダッシュボード'); }); |
この例では、/admin/dashboardのようなパスに対してのみログが記録されます。ルートのグループ化やセキュリティ制限に効果的です。
| 適用方法 | パス指定 | 適用範囲 | 用途例 |
|---|---|---|---|
app.use() |
- | 全リクエスト | セキュリティ、ログ |
router.use() |
/admin |
特定のルート | 認証、制限処理 |
エラー処理用ミドルウェアの設計パターン
Express.jsでは、4つのパラメータ(err, req, res, next)を受け取る関数をエラーハンドラとして定義できます。この仕組みにより、非同期処理やnext(err)呼び出しが発生した際に一元的にエラーをキャッチ・処理できるようになります。
4つのパラメータを受け取るエラーハンドラの構成
以下は基本的なエラーハンドラの実装例です:
|
1 2 3 4 5 6 7 8 9 10 |
app.use((err, req, res, next) => { console.error(err.stack); // エラー内容をJSON形式で返す res.status(500).json({ error: err.message, timestamp: new Date().toISOString() }); }); |
この構造では、next()ではなくerrパラメータを受け取ることで、エラーの種類に応じた処理(ログ出力やステータスコード設定)を柔軟に行えます。
スタックトレースの取得とログ出力
エラーハンドラ内でスタックトレースを取得するには、err.stackを使用します。ただし、生のスタックトレースはセキュリティリスクがあるため、開発環境用にのみ表示することが推奨されます。
|
1 2 3 4 5 6 7 |
if (process.env.NODE_ENV === 'development') { console.error('エラー詳細:', err.stack); } else { // セキュリティ対策: 適切なヘッダを設定し、スタックトレースは出力しない res.status(500).json({ error: 'Internal Server Error' }); } |
このようにして、運用環境では最小限の情報を出力することでセキュリティを強化できます。
実践的なミドルウェア実装例
ミドルウェアは具体的なニーズに応じてカスタマイズします。以下では、アクセスログ記録とJWT認証チェックの2つの例を通して、実装方法をステップバイステップで説明します。
リクエストログ記録ミドルウェア
アクセスログを出力するためには、リクエストの情報を取得し、next()で処理を続けるだけです。
|
1 2 3 4 5 6 7 8 |
function requestLogger(req, res, next) { const timestamp = new Date().toISOString(); console.log(`${timestamp} - ${req.method} ${req.url}`); next(); } app.use(requestLogger); |
このミドルウェアは、全てのリクエストに対して実行され、アクセス日時やメソッド、URLを記録します。ログの詳細度を上げるには、req.headersなども追加できます。
JWT認証チェックミドルウェア
JWTトークンを検証するためには、jsonwebtokenライブラリを使用します。以下のコードは、ヘッダに含まれたトークンを検証し、無効な場合はエラーを返す処理です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const jwt = require('jsonwebtoken'); function authenticateToken(req, res, next) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) return res.sendStatus(401); // 認証情報がない // シークレットは環境変数から取得 jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err) return res.status(403).json({ error: 'トークンが無効です' }); req.user = user; next(); }); } |
このミドルウェアは、特定のルート(例:/api/data)にのみ適用させることで、セキュリティを強化できます。
ベストプラクティスと注意点
カスタムミドルウェアを効果的に運用するには、いくつかのベストプラクティスがあります。特に、ミドルウェアの順序や再利用性に気を配ることが重要です。
ミドルウェアの順序に関する設計考慮
リクエスト処理フローにおいて、ミドルウェアの順序は処理結果に直接影響します。たとえば、ログ記録ミドルウェアを認証チェックミドルウェアより前に配置すれば、無効なトークンでもアクセスが記録されます。
重要ポイント: ミドルウェアの順序は「処理フローに与える影響」を考えながら慎重に決定してください。
再利用性のある構造への工夫
ミドルウェアを他のルートやアプリケーションで再利用可能にするには、以下のような工夫が有効です:
- 関数として外部ファイルに分離し、必要に応じてインポートして使用
- ミドルウェアのパラメータ(例:
role)を動的に設定できるようにする
|
1 2 3 4 5 6 7 8 9 10 |
// authMiddleware.js module.exports = (requiredRole) => { return (req, res, next) => { if (!req.user || req.user.role !== requiredRole) { return res.status(403).json({ error: 'アクセス権がありません' }); } next(); }; }; |
このようにすることで、「管理者専用」や「一般ユーザー向け」のミドルウェアを柔軟に適用できます。
まとめ
本記事では、Express.jsでカスタムミドルウェアを実装する際の手順と実践例を解説しました。以下のポイントを押さえることで、効率的なミドルウェア設計が可能になります:
- ミドルウェア関数は
req, res, nextパラメータを受け取る - グローバル処理には
app.use()、特定ルートにはrouter.use()を使う - エラー処理用ミドルウェアでは4つのパラメータを指定し、スタックトレースを適切に管理する
- ログ記録や認証チェックなどの実装例をステップバイステップで確認できる
- 順序と再利用性が設計のカギとなる
記事内で紹介したサンプルコードを参考に、自社アプリケーションでカスタムミドルウェアを導入してみましょう。