このレッスンのロックを解除するには、まず「レッスン2:Node.jsとExpressプロジェクトのセットアップ」を完了してください。前のレッスンに戻る

はじめに

API がわずかなエンドポイントを超えて成長すると、すべてを単一ファイルに詰め込むのはすぐに手に負えなくなります。Express には Router という組み込みツールがあり、リソースごとにルートを別々のモジュールに分割できます。これに controllers レイヤーを組み合わせれば、クリーンにスケールする構造が手に入ります。新しいチームメンバーは必要なものを見つけやすくなり、新規リソースの追加が既存コードに影響しません。

このレッスンでは、具体的なプロジェクトのレイアウトを順に確認します。routes は URL パターンと HTTP メソッドを定義し、controllers が実際のハンドラーロジックを保持し、app.js がそれらを結びつけます。この分離を早い段階で身につけることが、最も重要な習慣です。

layered architecture diagram showing Request flowing into app.js, then Router layer, then Controller layer, then a placeholder Data layer

プロジェクト構成

これから構築するディレクトリ構成は以下のとおりです。

text
project/
├── app.js
├── routes/
│ ├── users.js
│ └── products.js
└── controllers/
├── usersController.js
└── productsController.js

パターンはシンプルです。リソースごとに1つのルートファイル、リソースごとに1つのコントローラーファイル。ルートは薄く保ち、各エンドポイントを処理するコントローラー関数を宣言するだけにします。コントローラーは専念し、自リソースのリクエスト/レスポンスロジックのみを含めるようにします。

Express Router

Express Router は独立してルートを処理するミニアプリケーションを作成し、それを app.js のパスにマウントします。

まずは users のルートファイルから始めましょう。

javascript
// routes/users.js
const express = require('express');
const router = express.Router();
const usersController = require('../controllers/usersController');
router.get('/', usersController.getAllUsers);
router.get('/:id', usersController.getUserById);
router.post('/', usersController.createUser);
router.put('/:id', usersController.updateUser);
router.delete('/:id', usersController.deleteUser);
module.exports = router;

ルートでは `/users` や `/users/:id` ではなく、`/` や `/:id` のような相対パスを使用している点に注目してください。プレフィックスは app.js でルーターをマウントする際に追加されるため、同じパスを何度も書く必要はありません。

app.js でのルーターのマウント:

javascript
// app.js
const express = require('express');
const app = express();
app.use(express.json());
const usersRouter = require('./routes/users');
const productsRouter = require('./routes/products');
app.use('/users', usersRouter);
app.use('/products', productsRouter);
app.listen(3000, () => console.log('Server running on port 3000'));

GET リクエスト `/users/42` は `router.get('/:id', ...)` に解決され、`req.params.id` は `'42'` になります。クリーンで予測可能です。

Controllers

Controllers は名前付き関数をエクスポートする通常のモジュールです。各関数は標準的な Express request handler で、`req` と `res`(必要に応じて `next`)を受け取り、レスポンスを送信する責務を持ちます。

javascript
// controllers/usersController.js
// Simulated in-memory data — you will replace this with a database call later
const users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
];
exports.getAllUsers = (req, res) => {
res.json(users);
};
exports.getUserById = (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
res.json(user);
};
exports.createUser = (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ message: 'name and email are required' });
}
const newUser = { id: users.length + 1, name, email };
users.push(newUser);
res.status(201).json(newUser);
};
exports.updateUser = (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
const { name, email } = req.body;
if (name) user.name = name;
if (email) user.email = email;
res.json(user);
};
exports.deleteUser = (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({ message: 'User not found' });
}
users.splice(index, 1);
res.status(204).send();
};

ここで注目すべき点がいくつかあります。コントローラーは入力を検証し、問題があれば適切なステータスコードで早期リターンします。これにより正常系のコードが読みやすくなります。404チェックは明示的です。レコードが存在するかどうかは仮定しません。そして204 No Content(ボディなし)は、削除成功時の慣習的なレスポンスです。

flowchart showing a single incoming HTTP request being matched by Router to the correct Controller function, then branching into success response vs error response

よくあるミス

JSON リクエストボディのパースを忘れることが、初心者に最も多い問題です。app.js に `app.use(express.json())` がないと `req.body` は undefined になり、`createUser` や `updateUser` は何も受け取れないまま静かに動作してしまいます。

もう一つの落とし穴は、`req.params.id`(文字列)を数値の id と厳密等価で直接比較することです。`users.find(u => u.id === req.params.id)` のチェックは `'42' !== 42` のため常に失敗します。まず `parseInt` または `Number()` で変換してください。

最後に、レスポンスを送信した後に returnしないことで「Cannot set headers after they are sent」エラーが発生します。必ず `return res.status(404).json(...)` を使用してください。`return` があることで、Express は次の行へ進み2 回目のレスポンスを送信しようとしなくなります。

まとめ

Express Router を使うと、関連するルートを独自のモジュールにまとめ、app.js のプレフィックスにマウントできます。Controllers はハンドラーロジックをルートファイルから切り離し、各レイヤーの可読性・テスト容易性・拡張性を高めます。パターンは次のとおりです。ルートファイルは URL とメソッドを宣言し、コントローラー関数がロジックを処理してレスポンスを送信する。リソースごとに1 ファイルを守ることで、プロジェクトが成長してもこの構造は崩れずに保たれます。

レッスンの練習

レッスンのチェックポイント

学んだ内容を復習し、回答へのフィードバックを受けましょう。

0/1
回答済み · 0/1