Express.jsにおけるミドルウェアの活用方法

ミドルウェアの基本概念

Express.jsの大部分の機能は、ミドルウェアと呼ばれる関数によって実現されます。ミドルウェアは、クライアントからのリクエストを受信してからレスポンスを返すまでの間に実行される処理のことです。これにより、リクエストやレスポンスのオブジェクトを操作したり、次の処理に制御を渡したりすることができます。Expressは、このミドルウェアを柔軟に組み合わせることで、アプリケーションの機能を拡張していく設計思想になっています。

アプリケーション全体へのミドルウェア適用

全てのルートに対して共通の処理を行いたい場合、`app.use()` メソッドを利用します。このメソッドは、特定のパスを指定することもできますが、パスを省略した場合は全てのリクエストに対してミドルウェアが適用されます。ミドルウェア関数は、通常 `request`、`response`、`next` の3つの引数を受け取ります。`next()` を呼び出すことで、処理が次のミドルウェアまたはルートハンドラに渡されます。


const express = require('express');
const expressApp = express();

// 全てのリクエストに対してシンプルなロギングミドルウェアを適用
expressApp.use((request, response, next) => {
  console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`);
  next();
});

expressApp.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました');
});

特定のルートへのミドルウェア適用

特定のエンドポイントにのみミドルウェアを適用することも可能です。これにより、ルートごとに異なる前処理を施すことができます。例えば、特定のAPIルートでのみリクエストの実行時間を計測したい場合などに有効です。


const express = require('express');
const server = express();

// リクエスト処理時間を計測するミドルウェア
function requestTimer(request, response, next) {
  request.startTime = Date.now();
  next();
}

server.get('/api/user/profile', requestTimer, (request, response) => {
  const duration = Date.now() - request.startTime;
  response.send(`ユーザープロフィールページです。処理時間: ${duration}ms`);
});

server.listen(3000);

複数ミドルウェアの連携

一つのルートやアプリケーション全体に、複数のミドルウェアを連続して適用できます。ミドルウェアは記述された順に実行されるため、実行順序が重要になる場合があります。複数のミドルウェアを一度に適用するには、配列を使うとコードが整理しやすくなります。


const express = require('express');
const app = express();

// データ検証ミドルウェア
function validateData(request, response, next) {
  // 仮の検証ロジック
  if (request.query.id && isNaN(request.query.id)) {
    return response.status(400).send('IDは数値である必要があります。');
  }
  next();
}

// 認証ミドルウェア
function authenticate(request, response, next) {
  // 仮の認証ロジック
  const authHeader = request.headers['authorization'];
  if (!authHeader || authHeader !== 'Bearer my-secret-token') {
    return response.status(401).send('認証が必要です。');
  }
  next();
}

// 複数のミドルウェアを配列で適用
app.get('/secure/data', [validateData, authenticate], (request, response) => {
  response.send('認証とバリデーションに成功しました。');
});

app.listen(3000);

クエリ文字列の自動解析

Express 4.x以降では、URLに含まれるクエリ文字列(例: `?name=taro&age=30`)を自動的にJavaScriptオブジェクトに解析し、`request.query` プロパティとして利用可能にする機能が標準で組み込まれています。そのため、過去のバージョンで必要だった `query` ミドルウェアなどを別途追加する必要はありません。

静的ファイルの配信

CSS、JavaScript、画像ファイルといった静的なリソースを配信するには、`express.static` ミドルウェアを使用します。このミドルウェアは、指定されたディレクトリ内のファイルをクライアントへのリクエストに応じて直接提供します。


const express = require('express');
const path = require('path');
const app = express();

// 'public' ディレクトリを静的ファイルのルートとして設定
app.use(express.static(path.join(__dirname, 'public')));

// '/assets' というパスで 'assets' ディレクトリをマウント
app.use('/assets', express.static(path.join(__dirname, 'assets')));

// 例: http://localhost:3000/style.css -> public/style.css が配信される
// 例: http://localhost:3000/assets/logo.png -> assets/logo.png が配信される

app.listen(3000);

POSTリクエストのボディデータ処理

フォームから送信されたPOSTリクエストのボディを解析するには、`express.json()` と `express.urlencoded()` という組み込みミドルウェアを使用します。`express.json()` はJSON形式のペイロードを、`express.urlencoded()` はURLエンコードされたペイロードを解析します。


const express = require('express');
const app = express();

// JSONとURLエンコードされたボディを解析
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get('/register', (request, response) => {
  response.send(`
    <form method="POST" action="/register">
      ユーザー名: <input type="text" name="username"><br>
      メールアドレス: <input type="email" name="email"><br>
      <input type="submit" value="登録">
    </form>
  `);
});

app.post('/register', (request, response) => {
  const username = request.body.username;
  const email = request.body.email;
  response.send(`登録ありがとうございます、${username}さん。メールアドレス: ${email}`);
});

app.listen(3000);

Cookieの送受信

`cookie-parser` ミドルウェアを利用すると、リクエストに含まれるCookieの読み取りと、レスポンスへのCookieの設定が簡単に行えます。リクエストのCookieは `request.cookies` オブジェクトに、署名付きCookieは `request.signedCookies` オブジェクトに格納されます。レスポンスにCookieを設定するには `response.cookie()` を使用します。


const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();

app.use(cookieParser());

app.get('/', (request, response) => {
  // 'theme' というCookieがなければ設定する
  if (!request.cookies.theme) {
    response.cookie('theme', 'dark', {
      maxAge: 24 * 60 * 60 * 1000, // 1日
      httpOnly: true,
      path: '/'
    });
    response.send('ダークテーマが設定されました。');
  } else {
    response.send(`現在のテーマは ${request.cookies.theme} です。`);
  }
});

app.listen(3000);

セッション管理の実装

より堅牢なセッション管理を行うには、`express-session` ミドルウェアが一般的に使用されます。このミドルウェアは、セッション情報をサーバー側に保存し、クライアントにはセッションIDを識別するためのCookieを送信します。これにより、Cookieのみに情報を保存する場合よりもセキュリティが向上します。


const express = require('express');
const session = require('express-session');
const app = express();

app.use(session({
  secret: 'a-very-strong-secret-key',
  resave: false,
  saveUninitialized: true,
  cookie: { maxAge: 60 * 60 * 1000 } // 1時間
}));

app.get('/dashboard', (request, response) => {
  if (request.session.loggedIn) {
    request.session.views = (request.session.views || 0) + 1;
    response.send(`ようこそ!ダッシュボードへのアクセス回数: ${request.session.views}`);
  } else {
    response.redirect('/login');
  }
});

app.get('/premium-content', (request, response) => {
  if (request.session.loggedIn) {
    response.send('これはプレミアムコンテンツです。');
  } else {
    response.status(403).send('アクセスが拒否されました。');
  }
});

app.get('/login', (request, response) => {
  request.session.loggedIn = true;
  response.redirect('/dashboard');
});

app.get('/logout', (request, response) => {
  request.session.destroy(() => {
    response.redirect('/login');
  });
});

app.listen(3000);

ベーシックHTTP認証の適用

特定のルートへのアクセスを制限するために、ベーシックHTTP認証を簡単に実装できます。`express-basic-auth` などのパッケージを利用すると、少ないコードで認証機能を追加できます。認証に失敗すると、ブラウザは標準のログインダイアログを表示します。


const express = require('express');
const basicAuth = require('express-basic-auth');
const app = express();

// 認証用のユーザー情報
const users = {
  'admin': 'superSecretPassword',
  'editor': 'editorPass123'
};

// 管理者ページ全体に認証を適用
app.use('/admin', basicAuth({
  users: users,
  challenge: true, // ブラウザに認証ダイアログを表示
  unauthorizedResponse: '認証に失敗しました。'
}));

app.get('/admin/dashboard', (request, response) => {
  response.send('管理者用ダッシュボードへようこそ。');
});

// 特定のルートにのみ認証を適用
const auth = basicAuth({
  users: users,
  challenge: false // 401 Unauthorizedレスポンスのみ返す
});

app.get('/reports', auth, (request, response) => {
  response.send('レポートページです。');
});

app.listen(3000);

セッションを用いた認証システム

より実践的な認証システムとして、ログインフォームとセッションを組み合わせる方法があります。この方法では、ユーザーが送信した認証情報を検証し、成功した場合にセッションオブジェクト (`request.session`) にユーザー情報などを保存します。以降のリクエストでは、セッション情報に基づいてユーザーがログイン状態かどうかを判断します。パスワードのハッシュ化には、`bcrypt` などの強力なライブラリを使用することが推奨されます。


const express = require('express');
const session = require('express-session');
const bcrypt = require('bcrypt'); // bcryptを利用
const app = express();

app.use(express.urlencoded({ extended: true }));
app.use(session({
  secret: 'another-secret-for-session',
  resave: false,
  saveUninitialized: false,
}));

// 仮のユーザーデータベース(実際のアプリではDBを使用)
const users = {
  'testuser': bcrypt.hashSync('password123', 10) // ハッシュ化済みパスワード
};

app.get('/', (request, response) => {
  if (request.session.userId) {
    response.send(`ログイン中です。<a href="/logout">ログアウト</a>`);
  } else {
    response.send(`
      <h1>ログイン</h1>
      <form method="POST" action="/login">
        ユーザー名: <input type="text" name="username"><br>
        パスワード: <input type="password" name="password"><br>
        <input type="submit" value="ログイン">
      </form>
    `);
  }
});

app.post('/login', async (request, response) => {
  const { username, password } = request.body;
  const storedHash = users[username];

  if (storedHash && await bcrypt.compare(password, storedHash)) {
    // 認証成功
    request.session.userId = username;
    response.redirect('/');
  } else {
    // 認証失敗
    response.status(401).send('ユーザー名またはパスワードが違います。');
  }
});

app.get('/logout', (request, response) => {
  request.session.destroy((err) => {
    if (err) {
      return response.redirect('/');
    }
    response.clearCookie('connect.sid'); // デフォルトのクッキー名を削除
    response.redirect('/');
  });
});

app.listen(3000);

カスタムミドルウェアの作成

独自のミドルウェアを作成することも非常に簡単です。ミドルウェア関数は `request`, `response`, `next` の3つの引数を取る関数として定義します。この関数内で何らかの処理を行い、最後に `next()` を呼び出して次のミドルウェアまたはルートハンドラに制御を渡すのが基本です。`next()` を呼び忘れると、リクエスト処理がそこで停止してしまうため注意が必要です。


const express = require('express');
const app = express();

// リクエストヘッダーの 'x-api-key' をチェックするカスタムミドルウェア
function checkApiKey(request, response, next) {
  const apiKey = request.headers['x-api-key'];
  const validKey = 'expected-valid-api-key';

  if (apiKey && apiKey === validKey) {
    console.log('APIキーが正しいです。');
    next(); // キーが正しければ次へ
  } else {
    console.warn('無効なAPIキーです。');
    response.status(403).send('Forbidden: Invalid API Key'); // エラーレスポンスを返して処理を終了
  }
}

// '/api' で始まる全てのルートにカスタムミドルウェアを適用
app.use('/api', checkApiKey);

app.get('/api/data', (request, response) => {
  response.json({ message: 'これは保護されたAPIデータです。' });
});

app.get('/public/info', (request, response) => {
  response.send('これは公開情報です。');
});

app.listen(3000);

タグ: Express.js Node.js ミドルウェア Web開発 セッション管理

8月16日 14:53 投稿