フォルダ構成
Node.js Webアプリケーションのフォルダ構成
Section titled “Node.js Webアプリケーションのフォルダ構成”Node.jsのプロジェクトには公式な標準構成はありませんが、アプリケーションの規模に応じて、複数のベストプラクティスが存在します。これらは、コードの可読性、メンテナンス性、およびチーム開発の効率性を高めることを目的としています。
なぜフォルダ構成が重要なのか
Section titled “なぜフォルダ構成が重要なのか”問題のあるコード(構成がない場合)
Section titled “問題のあるコード(構成がない場合)”問題のあるコード:
// 問題: すべてのコードが1つのファイルに混在const express = require('express');const app = express();
// ユーザー関連のルートapp.get('/users/:id', async (req, res) => { const db = require('./db'); const user = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]); res.json(user);});
// 商品関連のルートapp.get('/products/:id', async (req, res) => { const db = require('./db'); const product = await db.query('SELECT * FROM products WHERE id = ?', [req.params.id]); res.json(product);});
// 問題点:// 1. ファイルが肥大化(1000行以上になる可能性)// 2. テストが困難(すべてが1つのファイルに混在)// 3. チーム開発が困難(コンフリクトが頻発)// 4. 再利用性が低い(コードの重複)解決: 適切なフォルダ構成
// 解決: レイヤーごとに分離const express = require('express');const router = express.Router();const userController = require('../controllers/userController');
router.get('/:id', userController.getUser);module.exports = router;
// controllers/userController.jsconst userService = require('../services/userService');
exports.getUser = async (req, res) => { const user = await userService.getUserById(req.params.id); res.json(user);};
// services/userService.jsconst userRepository = require('../repositories/userRepository');
exports.getUserById = async (id) => { return await userRepository.findById(id);};
// メリット:// 1. ファイルが小さく、理解しやすい// 2. 各レイヤーを独立してテスト可能// 3. チーム開発が容易(レイヤーごとに担当を分けられる)// 4. コードの再利用性が高い1. 小〜中規模向けの基本構成 (MVCベース)
Section titled “1. 小〜中規模向けの基本構成 (MVCベース)”この構成は、小規模から中規模のWebアプリケーションで最も広く採用されています。MVC (Model-View-Controller) の原則に基づいて、役割ごとにコードを分割します。
my-app/├── node_modules/ # npmパッケージ├── public/ # 公開する静的ファイル(CSS, JS, 画像など)├── src/ # アプリケーションのソースコード│ ├── controllers/ # リクエストの制御とロジック│ ├── models/ # データベース操作とデータ定義│ ├── routes/ # ルーティング設定│ └── views/ # テンプレートファイル(HTMLなど)├── .env # 環境変数├── .gitignore # Gitの管理から除外するファイル├── package.json # プロジェクトの設定└── server.js # アプリケーションの起動ファイルsrc/ ディレクトリ内に、controllers、models、routes、views といったMVCの各レイヤーを配置することで、各ファイルの役割が明確になります。
.env と package.json は、プロジェクト全体の設定を管理する上で不可欠なファイルです。
2. 大規模向けの高機能な構成
Section titled “2. 大規模向けの高機能な構成”アプリケーションの規模が大きくなると、ビジネスロジックや共通の処理をより詳細に分離する必要があります。以下のディレクトリを追加することで、コードの再利用性と管理のしやすさが向上します。
- src/services/: 複雑なビジネスロジックをコントローラーから分離します。ユーザー登録やメール送信など、複数のコントローラーで共通して使われる処理をここにまとめ、コードの再利用性とテスト性を高めます。
- src/middlewares/: リクエストの共通処理を管理します。認証、ログ記録、CORS(オリジン間リソース共有)設定など、リクエストがコントローラーに到達する前に実行される処理をここに配置し、コードの重複を防ぎます。
- src/utils/ or src/helpers/: 汎用的なヘルパー関数をまとめます。日付のフォーマット、データのバリデーション、共通の定数など、特定のレイヤーに属さないユーティリティ関数を格納します。
- src/config/: 詳細な環境設定を管理します。データベース接続情報やAPIキーなどを、環境(開発、テスト、本番)ごとに分けて管理します。
3. より高度な構成:機能(Feature)ベースの構造
Section titled “3. より高度な構成:機能(Feature)ベースの構造”アプリケーションが非常に大規模で、多くの独立した機能を持つ場合、MVCのレイヤー別分割よりも、機能(Feature)ごとにコードをまとめる構成が有効です。これにより、特定の機能の追加や削除が容易になり、チーム開発の際のコンフリクトを減らせます。
my-app/├── src/│ ├── modules/│ │ ├── users/ # ユーザー管理機能│ │ │ ├── user.controller.js│ │ │ ├── user.model.js│ │ │ └── user.routes.js│ │ └── products/ # 商品管理機能│ │ ├── product.controller.js│ │ ├── product.model.js│ │ └── product.routes.js│ └── shared/ # 共有モジュール│ ├── utils.js│ └── auth.middleware.js│ ...この構造では、usersやproductsといった各機能のディレクトリ内に、その機能に関連するすべてのファイル(コントローラー、モデル、ルートなど)を配置します。
フォルダ構成の選択判断
Section titled “フォルダ構成の選択判断”プロジェクト規模に応じた判断基準
Section titled “プロジェクト規模に応じた判断基準”小規模プロジェクト(< 10エンドポイント、1-2人):
my-app/├── src/│ ├── routes.js # すべてのルートを1つのファイルに│ ├── controllers.js # すべてのコントローラーを1つのファイルに│ └── models.js # すべてのモデルを1つのファイルに└── server.js判断基準:
- シンプルさを優先
- 過度な構造化は避ける
- 必要に応じて後からリファクタリング
中規模プロジェクト(10-50エンドポイント、3-5人):
my-app/├── src/│ ├── controllers/ # コントローラーを分離│ ├── models/ # モデルを分離│ ├── routes/ # ルートを分離│ ├── services/ # サービスレイヤーを追加│ └── middlewares/ # ミドルウェアを追加└── server.js判断基準:
- MVCパターンを採用
- サービスレイヤーでビジネスロジックを分離
- チーム開発を考慮した構成
大規模プロジェクト(50+エンドポイント、5+人):
my-app/├── src/│ ├── modules/ # 機能ごとにモジュール化│ │ ├── users/│ │ │ ├── user.controller.js│ │ │ ├── user.service.js│ │ │ ├── user.model.js│ │ │ └── user.routes.js│ │ └── products/│ └── shared/ # 共有モジュール│ ├── utils/│ ├── middlewares/│ └── config/└── server.js判断基準:
- 機能ベースの構成を採用
- モジュール間の依存関係を最小化
- スケーラビリティを最優先
実践的な意思決定フレームワーク
Section titled “実践的な意思決定フレームワーク”判断フローチャート:
1. プロジェクトの規模は? ├─ 小規模 → シンプルな構成 ├─ 中規模 → MVC構成 └─ 大規模 → 機能ベース構成
2. チームの規模は? ├─ 1-2人 → シンプルな構成 ├─ 3-5人 → MVC構成 └─ 5+人 → 機能ベース構成
3. 将来の拡張性は? ├─ 限定的 → シンプルな構成 ├─ 中程度 → MVC構成 └─ 高い → 機能ベース構成トレードオフ分析:
| 構成 | 学習コスト | 開発速度 | スケーラビリティ | 適用範囲 |
|---|---|---|---|---|
| シンプル | 低い | 高い | 低い | 小規模 |
| MVC | 中程度 | 中程度 | 中程度 | 中規模 |
| 機能ベース | 高い | 低い(初期) | 高い | 大規模 |
よくある間違いとその解決
Section titled “よくある間違いとその解決”間違い1: 過度な構造化
Section titled “間違い1: 過度な構造化”問題のあるコード:
// 問題: 小規模プロジェクトなのに過度に構造化my-app/├── src/│ ├── infrastructure/│ │ ├── database/│ │ │ ├── connections/│ │ │ └── migrations/│ │ └── cache/│ ├── domain/│ │ ├── entities/│ │ └── value-objects/│ └── application/│ ├── use-cases/│ └── dto/
// 問題点:// - 小規模プロジェクトには過剰// - 開発速度が低下// - 学習コストが高い解決: プロジェクト規模に応じた構成
// 解決: 小規模プロジェクトはシンプルにmy-app/├── src/│ ├── routes.js│ ├── controllers.js│ └── models.js間違い2: 構造化の不足
Section titled “間違い2: 構造化の不足”問題のあるコード:
// 問題: 大規模プロジェクトなのに構造化が不足my-app/├── src/│ └── app.js // 2000行のファイル
// 問題点:// - ファイルが肥大化// - テストが困難// - チーム開発が困難解決: 適切な構造化
// 解決: 機能ごとにモジュール化my-app/├── src/│ ├── modules/│ │ ├── users/│ │ └── products/どの構成を選ぶかは、プロジェクトの規模とチームのニーズによって異なります。プロジェクトの初期段階ではシンプルなMVC構成から始め、必要に応じて徐々に高度な構成に移行していくのが良いでしょう。
シニアエンジニアとして考慮すべき点:
- 段階的な進化: 完璧な構成を最初から作ろうとせず、必要に応じて進化させる
- チームの合意: 構成はチーム全体で理解し、維持できるものを選ぶ
- 一貫性: プロジェクト全体で統一された構成を維持
- ドキュメント化: 構成の理由をドキュメント化して、将来のメンテナンスを容易に