【初心者向け】Node.js/Expressでのroutes・controllers・middlewareの役割を勉強してみた

【初心者向け】Node.js/Expressでのroutes・controllers・middlewareの役割を勉強してみた

2025-04-255分で読めます

はじめに

Webアプリケーションのバックエンド開発において、特にNode.jsとExpressを使用する場合、routes、controllers、middlewareという3つの概念が頻繁に登場します。これらは一体何なのか、どのような役割を果たしているのか、初めて触れる方には分かりにくいかもしれません。

この記事では、これらの概念をレストランにたとえながら、初心者の方でも理解しやすいように解説していきます。

🍛 レストランにたとえて理解しよう!

あなたのアプリケーションを「レストラン」だと思ってみてください:

お客さん(=ブラウザやアプリの利用者)が
注文(=HTTPリクエスト)をして
シェフ(=コントローラー)が料理(=レスポンス)を作って
ウェイター(=ルーター)が注文を届けたり
店の警備員や受付(=ミドルウェア)が入店チェックをしたり
こんなイメージで考えてみましょう!

🔀 Routes(ルーター)=「注文を受ける係」「受付・案内係」

ルーターは、お客さんが「〇〇ください」と言ってきたときに:

「この注文はハンバーグ担当のシェフへ」
「この注文はドリンク担当へ」
と正しいコントローラー(担当)に振り分ける役割を担っています。

// routes/userRoutes.ts の例
import express from 'express';
import * as userController from '../controllers/userController';

const router = express.Router();

// /users というURLに来たらuserController.getListを実行
router.get('/users', userController.getList);

// /login というURLにPOSTで来たらuserController.loginを実行
router.post('/login', userController.login);

export default router;

これは「この /users というURLに来たら getList というコントローラーを実行しよう!」という意味です。

ルーターの主な仕事:

  • URLとHTTPメソッド(GET, POST, PUTなど)の組み合わせを定義
  • それぞれのURLに対応する処理(コントローラー)を指定
  • リクエストを適切なコントローラーに転送

👨‍🍳 Controllers(コントローラー)=「シェフ」

コントローラーは実際に料理(レスポンス)を作る人たちです。

たとえば /users にリクエストが来たら:

  • データベースからユーザー一覧を取得
  • それをキレイに整えて
  • 「これが注文の品です!」とレスポンスを返す

というのがコントローラーの仕事です。

// controllers/userController.ts の例
import { Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export const getList = async (req: Request, res: Response): Promise<void> => {
  // データベースからユーザー一覧を取得
  const users = await prisma.user.findMany();
  
  // JSONとしてレスポンスを返す
  res.json(users);
};

export const login = async (req: Request, res: Response): Promise<void> => {
  // ログイン処理を実装
  const { email, password } = req.body;
  // ...認証ロジック
  // 成功したらトークンを返すなど
};

コントローラーの主な仕事:

  • ビジネスロジックの実装
  • データベース操作
  • 適切なレスポンスの生成と返却
  • エラー処理

🛡️ Middleware(ミドルウェア)=「入口の警備員・前処理スタッフ」

ミドルウェアは、お客さん(リクエスト)が入ってくる前にチェックをします:

  • この人ログインしてる?
  • 注文票(リクエスト)はちゃんと記入されてる?
  • 記録(ログ)をとっておこう

といった処理をリクエストがコントローラーに届く前に実行するのがミドルウェアです。

// middleware/authMiddleware.ts の例
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

// JWT検証の戻り値の型定義
interface JwtPayload {
  userId: string;
  email: string;
}

// Request型を拡張して認証ユーザー情報を追加
declare global {
  namespace Express {
    interface Request {
      user?: JwtPayload;
    }
  }
}

// 認証ミドルウェアの例
export const checkAuth = (req: Request, res: Response, next: NextFunction): void => {
  // リクエストヘッダーからトークンを取得
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    res.status(401).json({ message: '認証が必要です' });
    return;
  }

  try {
    // トークンを検証
    const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as JwtPayload;
    req.user = decoded; // 認証情報をリクエストに追加
    next(); // 次のミドルウェアまたはコントローラーへ
  } catch (error) {
    res.status(401).json({ message: 'トークンが無効です' });
  }
};

アプリケーション全体に適用するミドルウェアの例

// app.ts または server.ts の例
import express from 'express';
import cors from 'cors';
import { checkAuth } from './middleware/authMiddleware';
import protectedRoutes from './routes/protectedRoutes';

const app = express();

// JSONリクエストボディを解析するミドルウェア
app.use(express.json());

// CORSを許可するミドルウェア
app.use(cors());

// ログを記録するミドルウェア
app.use((req: express.Request, res: express.Response, next: express.NextFunction): void => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
});

// ルーターを使用する前に認証ミドルウェアを適用
app.use('/api/protected', checkAuth, protectedRoutes);

ミドルウェアの主な仕事:

  • リクエストの前処理・検証
  • 認証・認可のチェック
  • リクエストボディの解析
  • ログ記録
  • CORS(Cross-Origin Resource Sharing)の処理
  • エラーハンドリング

3つの役割をつなげてみると…

リクエストの流れを図にすると次のようになります:

[ ユーザー ]
    ↓(リクエスト)
[ ミドルウェア ] ← ログインしてるかチェック
    ↓
[ ルーター ] ← URLに応じて処理を振り分け
    ↓
[ コントローラー ] ← 実際に処理してレスポンスを返す
    ↓
[ ユーザー ]

まとめ(ざっくり一言で)

  • routes: URLと処理のマッピング、注文票を見てどの担当に回すか決める
  • controllers: 実際の処理をする本体、料理を作るシェフ
  • middleware: 前処理やチェックをするスタッフ、入口の警備や受付

どんなときにこの構成を使うの?

この routes / controllers / middleware の構成は、主に Node.js × Express で Web API(バックエンド) を開発するときによく使われるパターンです。

🔧 どんなときに使う構成?

✅ Webアプリの「バックエンドAPI」を作るとき:

  • ログイン・サインアップの仕組みを作る
  • データベースに登録された「物件情報」や「ユーザー情報」を取得・更新する
  • ReactやNext.jsなどのフロントエンドとAPIで連携する

🏗 代表的な技術スタック(この構成で使うもの)

主な技術やライブラリ

補足

ルーティング

Express.js

app.use('/api/users', userRoutes); など

コントローラー

TypeScript / JavaScript

ビジネスロジックを書く場所

ミドルウェア

Express Middleware

express.json(), cors(), 独自の認証ミドルウェアなど

データベース操作

Prisma, Sequelize, Mongoose

ORMやODM:DBとやり取りする部分

フレームワーク

Node.js + Express.js

軽量で柔軟なWebフレームワーク

👀 具体的にこんなアプリで使われる

  • 管理者用ダッシュボード(例:マンション管理会社の管理画面)
  • フロントエンド(React/Next.jsなど)から使うAPI
  • スマホアプリのバックエンド
  • SaaSのサーバーサイドAPI

🧩 なぜこの構成が使われるのか?

  • 役割が分かれていて見通しがいい
  • 大規模開発でも保守しやすい
  • Expressが自由度高くてこの分割と相性がいい
  • 他の人とも分業しやすい(フロント、バック、認証、DB)

おわりに

この記事で解説した「routes・controllers・middleware」の構成は、Node.jsとExpressを使ったWeb API開発において基本的な構造です。役割を明確に分けることで、コードの可読性や保守性が高まり、チーム開発でも効率的に作業ができるようになります。

これから学びを深めていく中で、認証機能の追加、ファイルアップロード、フロントエンドとの連携など、さまざまな機能を実装する際にもこの基本構造が役立つことでしょう。