こんにちは、かつコーチです。
Expressのコードを読んでいると、必ずと言っていいほどapp.use()という記述に出会います。
「これって何をしているんだろう?」と疑問に思ったまま、なんとなくコピペしていませんか。
この記事では、Expressの中核概念であるミドルウェアについて、req/res/nextの流れから自作の方法まで解説します。
読み終えるころには、express.json()が何をしているのかも、自分でミドルウェアを書く方法も理解できるようになります。
ミドルウェアとは?
リクエストとレスポンスの間にある処理
ミドルウェア(middleware)とは、クライアントからのリクエストを受け取ってから、レスポンスを返すまでの間に実行される処理のことです。
Expressのアプリケーションは、リクエストが届くと複数のミドルウェアを順番に通過してから、最終的なレスポンスを返します。
イメージとしては、工場のベルトコンベアに近いです。
リクエストという「荷物」が流れてきて、各工程(ミドルウェア)で加工されながら、最後に完成品(レスポンス)として送り出されます。
ログを記録する、認証をチェックする、リクエストボディを解析するといった処理は、すべてミドルウェアとして実装されています。
req、res、nextの役割
ミドルウェアは、以下の3つの引数を受け取る関数として定義します。
- req(request):クライアントから送られてきたリクエストの情報が入ったオブジェクト
- res(response):クライアントに返すレスポンスを組み立てるためのオブジェクト
- next:次のミドルウェアに処理を渡すための関数
ここで最も重要なのがnextです。
next()を呼び忘れると、リクエストがそこで止まってしまい、レスポンスが永遠に返ってきません。
実際に私も学習初期にnext()を書き忘れて、ブラウザがずっと「読み込み中」のままになった経験があります。
その時のコードは以下のようなものでした。
// ❌ next()を呼び忘れた例
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
// next()を書き忘れている
});
app.get('/', (req, res) => {
res.send('こんにちは');
});
このコードを実行すると、ブラウザにアクセスしてもレスポンスが返ってこず、タイムアウトするまで待たされます。
コンソールにはログが出力されるのに、画面には何も表示されないので、原因が分かりにくいバグの1つです。
基本の書き方 / 実装手順
express.json()の役割
Expressには、あらかじめ用意された組み込みミドルウェアがいくつかあります。
その代表格がexpress.json()です。
クライアントがJSON形式のデータをPOSTやPUTで送ってきたとき、そのデータは最初は文字列(正確にはバイナリのストリーム)として届きます。
express.json()を使うと、このデータを自動的に解析して、req.bodyとしてJavaScriptのオブジェクトに変換してくれます。
const express = require('express');
const app = express();
// JSON形式のリクエストボディを解析するミドルウェア
app.use(express.json());
app.post('/users', (req, res) => {
console.log(req.body); // { name: '田中太郎', age: 25 } のように取得できる
res.status(201).json({ message: '登録しました', data: req.body });
});
app.listen(3000, () => {
console.log('サーバーを起動しました');
});
app.use(express.json())を書き忘れると、req.bodyはundefinedになってしまいます。
POSTのAPIを作ったのにreq.bodyが読めない、という相談は初心者からよく受けますが、原因の多くはこのミドルウェアの書き忘れです。
自作ミドルウェアの書き方
自分でミドルウェアを作るのも、それほど難しくありません。
(req, res, next) => {}という形の関数を定義するだけです。
以下は、リクエストのメソッドとURL、処理時間をログに出力するミドルウェアの例です。
// カスタムミドルウェア:アクセスログを出力する
function accessLogger(req, res, next) {
const start = Date.now();
console.log(`[受信] ${req.method} ${req.url}`);
// レスポンス送信後に処理時間を計測する
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`[完了] ${req.method} ${req.url} - ${duration}ms`);
});
next(); // 次の処理へ進める
}
app.use(accessLogger);
このように、ミドルウェアは「共通処理を1箇所にまとめて、複数のルートで使い回す」ための仕組みです。
認証チェックや入力値のバリデーションなど、複数のエンドポイントで共通して必要になる処理は、積極的にミドルウェア化すると保守性が上がります。
特定のルートだけに適用する方法
app.use()はアプリ全体に適用されますが、特定のルートだけにミドルウェアを適用することも可能です。
function checkAuth(req, res, next) {
const token = req.headers['authorization'];
if (!token) {
return res.status(401).json({ error: '認証が必要です' });
}
next();
}
// このルートだけ認証チェックを通す
app.get('/admin', checkAuth, (req, res) => {
res.send('管理者ページです');
});
このように、ルート定義の第2引数にミドルウェアを渡すと、そのルートだけに処理を適用できます。
よくあるつまずきポイント・エラー対処
ミドルウェアの登録順序を間違える
Expressのミドルウェアは、登録した順番に実行されるという性質があります。
この順序を意識せずにコードを書くと、思わぬ不具合が発生します。
// ❌ Before:認証チェックの前にルート処理が実行されてしまう
app.get('/admin', (req, res) => {
res.send('管理者ページです');
});
app.use(checkAuth); // 手遅れ。すでにルートが処理された後
上記の例では、checkAuthを登録する前に/adminのルートが定義されているため、認証チェックが機能しません。
// ✅ After:認証チェックを先に登録する
app.use(checkAuth);
app.get('/admin', (req, res) => {
res.send('管理者ページです');
});
ミドルウェアは基本的に「共通処理は先に、個別のルートは後に」登録すると覚えておくと迷いません。
express.json()を忘れてreq.bodyがundefinedになる
先ほども触れましたが、これは実務でも頻発するミスです。
// ❌ Before:express.json()を登録していない
const express = require('express');
const app = express();
app.post('/login', (req, res) => {
console.log(req.body); // undefined になってしまう
res.send('OK');
});
私自身、Postmanでリクエストを送ってもreq.bodyがundefinedのままで、30分ほど原因を探した経験があります。
原因は単純で、express.json()を登録し忘れていただけでした。
// ✅ After:express.json()を先に登録する
const express = require('express');
const app = express();
app.use(express.json());
app.post('/login', (req, res) => {
console.log(req.body); // { email: '...', password: '...' } と取得できる
res.send('OK');
});
req.bodyがundefinedになったら、まずexpress.json()の登録漏れを疑ってみてください。
まとめ
この記事のポイント
- ミドルウェアは、リクエストとレスポンスの間で実行される処理のこと
req、res、nextの3つの引数を受け取る関数として定義するnext()を呼び忘れると、リクエストが止まったままになるexpress.json()は、JSONのリクエストボディをreq.bodyに変換する組み込みミドルウェア- ミドルウェアは登録した順番に実行されるため、順序を意識する必要がある
次に読むべき記事
- Expressのルーティングの基本(GET/POST/パラメータ)
- ルーターを分割して整理する(express.Router()の使い方)
- エラーハンドリングミドルウェアで一元的にエラーを処理する
タグ: Express, 中級者向け, フレームワーク基礎