はじめに
Express.jsでWebアプリケーションを開発していると、ルーティングファイルが肥大化してしまうことはありませんか?この記事では、コントローラーを使ってコードを整理する方法を解説します。
この記事で学べること
- コントローラーの概念と役割
- ルーティングとビジネスロジックの分離方法
- MVCパターンにおけるコントローラーの位置づけ
対象読者
- Express.jsの基本的なルーティングを理解している方
- コードの整理方法を学びたい方
- MVCパターンに興味がある方
コントローラーとは何か
コントローラーの定義
コントローラーは、ルーティングから分離したビジネスロジックを指します。
ビジネスロジックとは、アプリケーション固有の処理のことです。例えば、データベースへの保存、データの加工、ユーザー権限のチェックなどが該当しますね。
なぜルーティングから分離するのか
ルーティングファイルにすべてのロジックを書くと、以下のような問題が発生します:
- ファイルが長くなり、可読性が低下する
- 同じ処理を複数の場所で重複して書いてしまう
- テストが難しくなる
- メンテナンスが困難になる
コントローラーに分離することで、これらの問題を解決できます。
具体例で理解するコントローラー
実際のコードを見ながら、コントローラーの役割を理解しましょう。
ルーター側のコード
// routes/campgrounds.js
router.route('/')
.post(isLoggedIn, upload.array('image'), validateCampground, catchAsync(campgrounds.createCampground));
// ↑ コントローラー関数を呼び出し
ルーターの役割は、URLとHTTPメソッドを対応するコントローラー関数に紐付けることです。ここではcampgrounds.createCampgroundというコントローラー関数を呼び出しています。
ミドルウェアの役割:
-
isLoggedIn: ログイン状態の確認 -
upload.array('image'): 画像ファイルのアップロード処理 -
validateCampground: データのバリデーション -
catchAsync: エラーハンドリング
コントローラー側のコード
// controllers/campgrounds.js
module.exports.createCampground = async (req, res) => {
// Campgroundモデルのインスタンスを作成
const campground = new Campground(req.body.campground);
// アップロードされた画像データを整形
campground.images = req.files.map(f => ({ url: f.path, filename: f.filename }));
// ログイン中のユーザーを作成者として設定
campground.author = req.user._id;
// データベースに保存
await campground.save();
console.log(campground);
// 成功メッセージを設定
req.flash('success', '新しいキャンプ場を登録しました');
// 詳細ページにリダイレクト
res.redirect(`/campgrounds/${campground._id}`);
}
コントローラーには、具体的な処理がすべて記述されています。
ビジネスロジックとは
先ほどのコントローラー関数には、以下のようなビジネスロジックが含まれています。
各処理の意味と役割
データベースモデルの作成
const campground = new Campground(req.body.campground);
リクエストボディからキャンプ場データを取得し、Mongooseモデルのインスタンスを作成します。
画像データの変換・整形
campground.images = req.files.map(f => ({ url: f.path, filename: f.filename }));
アップロードされた複数の画像ファイルを、データベースに保存できる形式に変換しています。
ユーザー情報との紐付け
campground.author = req.user._id;
現在ログインしているユーザーのIDを、キャンプ場の作成者として設定します。
データベースへの保存
await campground.save();
作成したキャンプ場データをMongoDBに保存します。
フラッシュメッセージの設定
req.flash('success', '新しいキャンプ場を登録しました');
次のページで表示される成功メッセージを設定します。
レスポンスの決定
res.redirect(`/campgrounds/${campground._id}`);
保存したキャンプ場の詳細ページにリダイレクトします。
これらすべてがビジネスロジックであり、アプリケーション固有の処理です。
MVCパターンにおけるコントローラーの位置づけ
コントローラーは、MVCパターンの中心的な役割を果たします。各層の関係を図で確認してみましょう。
各層の役割
Router (ルーティング層)
- URLとHTTPメソッドを受け取る
- 適切なコントローラー関数を呼び出す
- ミドルウェアを適用する
Controller (コントローラー層)
- ビジネスロジックを実行する
- モデルを操作してデータを取得・保存する
- ビューに渡すデータを準備する
- レスポンスの種類を決定する
Model (モデル層)
- データベースとのやり取りを担当する
- データの構造を定義する
- データの検証を行う
View (ビュー層)
- HTMLをレンダリングする
- コントローラーから受け取ったデータを表示する
リクエストからレスポンスまでの流れ
この流れを見ると、コントローラーが中心となって各層を連携させていることが分かりますね。
まとめ
コントローラーを使うメリット
コードの整理
ルーティングファイルがシンプルになり、見通しが良くなります。
再利用性の向上
同じビジネスロジックを複数のルートで使い回せます。
テストのしやすさ
ビジネスロジックが独立しているため、単体テストが書きやすくなります。
保守性の向上
機能ごとにファイルが分かれているため、修正箇所を見つけやすくなります。
実装時のポイント
- コントローラー関数は1つの責務に集中させる
- エラーハンドリングを適切に行う
- ビジネスロジックをモデルに寄せすぎない
- コントローラーとルーターの境界を明確にする
コントローラーパターンを理解すると、大規模なアプリケーション開発でもコードを整理しやすくなります。まずは小規模なプロジェクトから導入してみてはいかがでしょうか。