404 Not Found

404 Not Found


nginx

エクスプレス・ルーティングとミドルウェア

チャーリーのAPIプロジェクトは当初、ルートが5つしかなく、すべてapp.jsで記述されていたため、まだかなり分かりやすかった。2か月後、ルートの数は30に増え、ファイルは500行を超え、APIエンドポイントに1つの変更を加えるだけでも途方もなく時間がかかるようになってしまった。そこでチームは、express.Router() を使用してルートをモジュールに分割することを決定しました。ユーザー関連のルートは users.js に、認証関連のルートは auth.js に配置し、カスタムロギングミドルウェアは別のファイルに移しました。分割後、各ファイルは単一の責任を持つようになり、メンテナンスの効率が大幅に向上しました。

学習内容:


1. express.Router() モジュール型ルーティング

ルート数が増えるにつれて、すべてのルートを app.js に格納すると、ファイルが肥大化し、共同作業が困難になる可能性があります。express.Router() を使用すると、独立したルートインスタンスを作成し、それらをアプリケーション内の特定のパスプレフィックスにマウントすることができます。

機能 アプリ内で直接ルートを定義 Routerモジュールによるモジュール式ルーティング
ファイルの構成 すべてを app.js にまとめる モジュールごとに別々のファイルに分ける
パスプレフィックス 各ルートのフルパスを指定 マウント時に統一されたプレフィックスを設定
チームでの共同作業 複数のユーザーによる同一ファイルの編集 各モジュールの独立したメンテナンス
再利用性 低い 高い;プロジェクト間で再利用可能

▶ 例:スタンドアロン型ユーザールーティングモジュールの作成

JAVASCRIPT
// 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 へのルートモジュールのマウント

JAVASCRIPT
// 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

JAVASCRIPT
app.get('/products/:id', (req, res) => {
  const productId = req.params.id;
  res.json({ productId });
});

▶ 例:クエリ文字列 req.query

JAVASCRIPT
app.get('/products', (req, res) => {
  const { category, sort, page } = req.query;
  res.json({ category, sort, page: page || 1 });
});

/products?category=electronics&sort=price&page=2req.query、および{ category: 'electronics', sort: 'price', page: '2' }にアクセスしてください。

▶ 例:リクエスト本文 req.body および express.json()

JAVASCRIPT
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 の核心となる概念です。すべてのリクエストは一連のミドルウェアを通過し、各ミドルウェア関数はリクエストを読み取ったり、レスポンスを変更したり、次のミドルウェア関数に制御を委譲したりすることができます。

100%
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]

▶ 例:最もシンプルなカスタムミドルウェア

JAVASCRIPT
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 の統合

JAVASCRIPT
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) トリガー
登録場所 任意の場所 最後に配置する必要があります
複数あることはありますか? はい はい、順番に実行されます

▶ 例:グローバルエラー処理ミドルウェア

JAVASCRIPT
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({
    error: err.message || 'Internal Server Error'
  });
});

▶ 例:ルートでエラーを発生させる

JAVASCRIPT
app.get('/admin', (req, res, next) => {
  const err = new Error('Access denied');
  err.status = 403;
  next(err);
});

6. Mermaid:ルーターのモジュール式ルーティング構造

100%
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 — カスタムロギングミドルウェア

JAVASCRIPT
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 — ユーザー・ルート・モジュール

JAVASCRIPT
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 — 認証ルーティングモジュール

JAVASCRIPT
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 — 統合のエントリポイント

JAVASCRIPT
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');
});

テストを実行:

BASH
node app.js
TEXT
Server running on port 3000
GET /users 200 3ms
GET /users/1 200 2ms
POST /auth/login 200 5ms

❓ よくある質問

Q next() が呼び出されなかった場合はどうなりますか?
A リクエストは中断され、クライアントにはレスポンスが送信されません。制御を移行するには、ミドルウェア内で next() を呼び出すか、res.end()res.json() などのメソッドを使用してレスポンスを直接終了させる必要があります。
Q ルートパラメータとクエリパラメータの違いは何ですか?
A ルートパラメータはURLパスの一部であり、たとえば /users/:id にある「42」などがこれにあたります。これは req.params.id を使って取得できます。一方、クエリパラメータは「?」の後に続くキーと値のペアであり、たとえば /users?role=admin などがこれにあたります。これは req.query.role を使って取得できます。前者はリソースを識別し、後者はリソースをフィルタリングします。
Q ミドルウェアの登録順序は重要ですか?
A 非常に重要です。Expressは、ミドルウェアを登録された順序で実行します。例えば、express.json()は、req.bodyを読み込む必要があるルートよりも先に登録する必要があります。そうしないと、req.bodyundefinedになってしまいます。
Q グローバルなエラー処理を実装するにはどうすればよいですか?
A 4つの引数(err、req、res、next)を持つ関数を定義し、すべてのルートとミドルウェアの後にapp.use()で登録します。next(err)を呼び出すミドルウェアはすべて、このハンドラーにルーティングされます。
Q CORSミドルウェアはどのような問題を解決するのですか?
A ブラウザの同一オリジンポリシーは、クロスオリジンリクエストをブロックします。CORSミドルウェアは、レスポンスヘッダーにAccess-Control-Allow-Originなどのフィールドを追加することで、指定されたオリジンからのフロントエンドアプリケーションがAPIにアクセスできるようにします。
Q express.Router()app.get()を直接使用する場合の違いは何ですか?
A Routerは、任意のルートプレフィックスにマウントできる独立したルーターインスタンスを作成するため、モジュール化に適しています。一方、app.get()はアプリケーションインスタンスに直接登録されるため、シンプルなプロジェクトに適しています。機能的にはどちらも同等ですが、違いは構成方法にあります。
Q ミドルウェア内の req オブジェクトをどのように変更すれば、後続の処理にデータを渡すことができますか?
A req に、req.user = { id: 1 } などのプロパティを直接追加するだけです。そうすれば、後続のミドルウェアやルートは req.user を通じてこのデータにアクセスできるようになります。これは、Express におけるミドルウェア間の通信でよく使われるパターンです。

📖 まとめ


📝 練習問題

  1. このレッスンにあるすべてのコード例を完成させ、それぞれが正しく動作することを確認してください。
  2. 包括的な例を修正し、独自の拡張機能を追加する
  3. 公式ドキュメントを確認し、このレッスンで扱われていないAPIを1~2つ見つけ、それらのテストコードを作成してください。
  4. 振り返り:このレッスンで学んだことを、実際のプロジェクトにどのように活かしますか?
  5. このレッスンで学んだことと、これまでのレッスンの内容を組み合わせて、小さなプロジェクトを作成してみてください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%