0.はじめに
プログラムの開発をしていると、良く耳にする「ミドルウェア」
何となくわかってるような、わかってないような
凄くふんわりした感じで理解していたのですが、
G'sという(どちらかというと起業向け)
プログラミングスクールのNext.js講座の課題で、
ミドルウェアについて調べることになり、
せっかくなので、調べたことを記事としてアウトプットしようと思い、
本記事を作成する運びとなった次第です。
本記事は、一般的なミドルウェアについてではなく、
現在受講しているNext.jsとNode.jsのミドルウェア機能について
深堀りしたものになります。
1.ミドルウェアとは?
課題をやるにあたり、一般的に言われる「ミドルウェア」と
Node.jsやNext.jsで言われる「ミドルウェア」って、
異なる意味合いなんだろうと思ったので、
まずは一般的に言われている「ミドルウェア」について調べてみました。
1.1 一般的に言われるミドルウェア
ネットで調べてみたら、以下の回答が返ってきました。
ミドルウェアとは、オペレーティングシステム(OS)とアプリケーションソフトの間に位置し、
両者を繋ぐ役割を果たすソフトウェアのことです。
アプリケーション間の連携をスムーズにし、OSにはない特別な機能を提供することで、
アプリケーション開発の効率化やシステム全体の柔軟な構築を可能にします。
例としては、Webサーバーやデータベース管理システム(DBMS)が挙げられ、
これらは様々なシステムを統合し、ユーザーにシームレスなサービスを提供します。
ハードウェア
└ OS(Linux, Windows)
└─【ミドルウェア】←ここが土台(共通機能)
└ アプリケーション(例:ECサイト、社内ツール)
ミドルウェアの主な役割
■OSとアプリケーションの橋渡し:
OSが提供する基本的な機能だけでは難しい、
アプリケーション間の通信やデータ連携を仲介・実現します。
■システム連携の促進:
異なるシステムやプラットフォームを接続し、
相互にやり取りできる「接着剤」のような役割を担います。
■開発効率の向上:
アプリケーションがミドルウェアに処理を任せることで、
個別のアプリケーションは本来の機能に集中でき、
開発の手間やコストを削減できます。
■機能の標準化:
共通のインターフェースを提供し、
異なる技術や環境でもアプリケーションが連携しやすくなります。
■システム全体の統合:
様々なアプリケーションやデータベースを統合し、
ユーザーに単一の統合されたサービスを提供します。
ミドルウェアの種類
■Webサーバー:
Webページなどのリクエストに応じ、静的なコンテンツを配信します。
• 例:Nginx, Apache httpd
■アプリケーションサーバー:
動的なWebコンテンツ(ECサイトのカート機能など)を生成・配信します。
• 例:Tomcat, WildFly, Jetty
• Node.js/ExpressやLaravel(PHP-FPM)は
「ランタイム/実行環境+フレームワーク」寄りですが、
役割的にアプリ層を支える存在
■データベース管理システム(DBMS):
アプリケーションのデータ管理をサポートします。
• 例:MySQL, PostgreSQL, MariaDB, Oracle, SQL Server
■メッセージブローカー:
アプリケーション間でメッセージをやり取りする機能に特化しています。
• 例:RabbitMQ, Apache Kafka, Amazon SQS
1.2 一般的に言われる「ミドルウェア」のイメージ図
SE20年以上、やってますが、
Webサーバー、DBサーバーがミドルウェアに属するのを
初めて知りました!お恥ずかしい!!!
2.Node.js(Express)のミドルウェアとは?
Node.js(Express)のミドルウェアとは、
「リクエストとレスポンスの間で実行する関数」 のこと。
(わかりづらい!!!!!)
もう少しわかりやすく言うと、
Node.js(Express)のミドルウェアとは、
HTTPリクエストを受け取った後に実行する、
ログ記録・認証・ボディ解析・エラーハンドリングなどの共通機能を指す。
(DBのCRUDやバリデーションはビジネスロジックというらしい)
Expressのドキュメントでもミドルウェアの仕組みが公式に説明されています。
2.1 ミドルウェアの基本構造
// ミドルウェア関数の基本形
function middleware(req, res, next) {
// 前処理
console.log('ミドルウェアが実行されました');
// 次のミドルウェアに処理を渡す
next();
}
■パラメータの説明
req:リクエストオブジェクト(クライアントからの要求情報)
res:レスポンスオブジェクト(クライアントへの返答情報)
next:次のミドルウェアに処理を渡す関数
2.2 ミドルウェアのエラーハンドラー
expressでは、デフォルトのエラーハンドラーを提供しているが、
ミドルウェアのエラーハンドラーをカスタムで定義する場合は、
最後に定義しないといけないらしい。
const express = require('express');
const app = express();
// 1. 通常のミドルウェア
app.use(loggerMiddleware);
// 2. 認証ミドルウェア
app.use(authMiddleware);
// 3. ルート定義
app.get('/api/users', (req, res) => {
// ここでエラーが発生する可能性
throw new Error('何かエラーが発生しました');
});
// 4. 404ハンドラー(存在しないルートの場合)
app.use((req, res) => {
res.status(404).json({ error: 'ページが見つかりません' });
});
// 5. エラーハンドリングミドルウェア(必ず最後)
app.use((err, req, res, next) => {
console.error('ERROR:', err);
res.status(500).json({
error: process.env.NODE_ENV === 'production' ? 'Internal Server Error' : err.message
});
});
ミドルウェア処理が成功したら、next()で次の処理を行い、
失敗したらエラーハンドラーでフロント側にエラーを返すと理解しました。
因みに、エラーハンドラーでは引数が(err, req, res, next)の
4つ必要らしい。
2.3 具体的な実装例
2.3.1 ログ記録ミドルウェア
// カスタムログミドルウェア
const loggerMiddleware = (req, res, next) => {
const start = Date.now();
// レスポンス完了時の処理
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} ${res.statusCode} - ${duration}ms`);
});
next();
};
app.use(loggerMiddleware);
2.3.2 認証ミドルウェア
// JWT認証ミドルウェア
const jwt = require('jsonwebtoken');
const authMiddleware = (req, res, next) => {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'トークンが提供されていません' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: '無効なトークンです' });
}
};
// 保護されたルートに適用
app.get('/protected', authMiddleware, (req, res) => {
res.json({ message: '認証されたユーザーのみアクセス可能', user: req.user });
});
2.3.3 CORS対応ミドルウェア
// CORS設定ミドルウェア
const corsMiddleware = (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// プリフライトリクエストの処理
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
};
app.use(corsMiddleware);
2.4 Node.js(Express)のミドルウェアイメージ図
複数ミドルウェア機能があった場合は上から処理を行って、
成功したらnext()で次のミドルウェアの処理を行う。
※エラー発生時は、エラーハンドラーを呼び出してエラー内容をフロントに返す。
処理フロー図とシーケンス図を見て、より具体的なイメージが出来ました。
3.Next.jsのミドルウェアとは?
公式ドキュメントに書かれてた内容を翻訳すると、
「middleware.js(ts)ファイルは、ミドルウェアを記述し、
リクエストが完了する前にサーバー上でコードを実行するために使用されます。
その後、受信したリクエストに基づいて、
リクエストまたはレスポンスのヘッダーを書き換え、
リダイレクト、変更、または直接応答することで、レスポンスを変更できます。」
「ミドルウェアはルートがレンダリングされる前に実行されます。
これは、認証、ログ記録、リダイレクト処理などの
カスタムサーバーサイドロジックを実装する場合に特に便利です。」
「プロジェクトルート、または該当する場合はsrc 内に、
pages.js(ts) または app.js(ts)と同じ階層に
middleware.ts ファイル(または .js ファイル)を作成する。」
となっています。
うーん、何となくわかるが、、、説明がわかりづらい。
■Next.js公式ドキュメント(ミドルウェアの説明ページ)
3.1 ミドルウェアの基本構造
3.1.1 ファイルの配置
プロジェクト直下(app/pages と同じ階層)に middleware.ts(または .js)を作成。
src/を使う場合はsrc/middleware.ts(または、.js)でもOK!
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
// ここにミドルウェアの処理を書く
return NextResponse.next()
}
// どのパスでミドルウェアを実行するかを指定
export const config = {
matcher: '/api/:path*' // 例:/api/で始まるすべてのパス
}
middlewareの関数内に処理を書いて、
成功したらNextResponse.next()で次の処理を行う流れっぽい。
3.2 具体的な実装例
3.2.1 認証ガード(未ログインの場合、ログイン画面にリダイレクト)
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// ダッシュボード配下はログイン必須という想定
if (pathname.startsWith('/dashboard')) {
const session = request.cookies.get('session')?.value;
if (!session) {
const loginUrl = new URL('/login', request.url);
// もともとの行き先をクエリに保持(戻り先用)
loginUrl.searchParams.set('next', pathname);
return NextResponse.redirect(loginUrl);
}
}
// 上流(Route Handler/SSRなど)にヘッダーを引き継ぐ例
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-middleware-passed', 'true');
return NextResponse.next({
request: { headers: requestHeaders }, // ← 上流に渡すのはこの形
});
}
// /dashboard配下のみにミドルウェア処理を適用
export const config = {
matcher: ['/dashboard/:path*'],
};
3.2.2 CORS
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
const allowedOrigins = ['https://acme.com', 'https://my-app.org'];
const corsHeaders = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
export function middleware(request: NextRequest) {
const origin = request.headers.get('origin') ?? '';
const isAllowed = allowedOrigins.includes(origin);
const isPreflight = request.method === 'OPTIONS';
if (isPreflight) {
const preflight = {
...(isAllowed && { 'Access-Control-Allow-Origin': origin }),
...corsHeaders,
};
return NextResponse.json({}, { headers: preflight });
}
const res = NextResponse.next();
if (isAllowed) res.headers.set('Access-Control-Allow-Origin', origin);
Object.entries(corsHeaders).forEach(([k, v]) => res.headers.set(k, v));
return res;
}
export const config = {
matcher: '/api/:path*',
};
3.3.3 Matcherの書き方
- 配列で複数パス、:path* などのワイルドカード、正規表現が使える
- 除外(ネガティブマッチ) を使うケースがある(例:静的ファイルや/apiを除外)
※記載例
export const config = {
matcher: [
// api/_next/画像系などを除外し、それ以外すべてに適用
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
};
- matcher は定数で書く必要があります(ビルド時解析のため)
3.4 利用時の注意点
- 1つのmiddleware.tsに1関数(同ファイルに複数は不可)
実行対象はmatcherで切り分け。 -
実行順序を意識:
next.config.mjsのheaders/redirects → Middleware →
その後にファイルシステムルート…の順。競合を避ける設計にすること
例えば、- next.config.mjs → 静的・固定の設定(常に同じリダイレクトやヘッダー)
- middleware.ts → 動的・条件付きの処理
(ユーザーごとに違う、Cookieや認証で分岐)など
-
大きいヘッダーはNG:
サーバー設定によっては 431 Request Header Fields Too Large になる
(トークン等は短く)
3.5 使い方の整理
- middleware.ts をプロジェクト直下に作成(src/直下でも可)
- まずは**最小の判定 + NextResponse.next()**で通る経路を作る
- matcher で対象パスを絞る(静的ファイルと/apiは除外が定番)
- ユースケースごとに リダイレクト / リライト / Cookie・ヘッダー操作を実装
- Next.jsのミドルウェア機能には標準のエラーハンドラーは無いので、
try〜Catchや、エラーページのリダイレクトなど実装する必要がある。
3.6 ミドルウェア機能のイメージ図
4.まとめ
「ミドルウェア」という用語は、
割と広義な意味合いを持つIT用語で、
本記事を作成したとて、多分100%は理解してないんだろうなと感じました。
とりあえず、一般的に言われる「ミドルウェア」
Node.js(Express)、Next.jsのミドルウェア機能については、
記事作成前よりも理解は深められたと思います。