Skip to content

フォルダ構成

Node.js Webアプリケーションのフォルダ構成

Section titled “Node.js Webアプリケーションのフォルダ構成”

Node.jsのプロジェクトには公式な標準構成はありませんが、アプリケーションの規模に応じて、複数のベストプラクティスが存在します。これらは、コードの可読性、メンテナンス性、およびチーム開発の効率性を高めることを目的としています。

なぜフォルダ構成が重要なのか

Section titled “なぜフォルダ構成が重要なのか”

問題のあるコード(構成がない場合)

Section titled “問題のあるコード(構成がない場合)”

問題のあるコード:

server.js
// 問題: すべてのコードが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. 再利用性が低い(コードの重複)

解決: 適切なフォルダ構成

routes/users.js
// 解決: レイヤーごとに分離
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
router.get('/:id', userController.getUser);
module.exports = router;
// controllers/userController.js
const userService = require('../services/userService');
exports.getUser = async (req, res) => {
const user = await userService.getUserById(req.params.id);
res.json(user);
};
// services/userService.js
const 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の各レイヤーを配置することで、各ファイルの役割が明確になります。

.envpackage.json は、プロジェクト全体の設定を管理する上で不可欠なファイルです。

アプリケーションの規模が大きくなると、ビジネスロジックや共通の処理をより詳細に分離する必要があります。以下のディレクトリを追加することで、コードの再利用性と管理のしやすさが向上します。

  • 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 “プロジェクト規模に応じた判断基準”

小規模プロジェクト(< 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中程度中程度中程度中規模
機能ベース高い低い(初期)高い大規模

問題のあるコード:

// 問題: 小規模プロジェクトなのに過度に構造化
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

問題のあるコード:

// 問題: 大規模プロジェクトなのに構造化が不足
my-app/
├── src/
│ └── app.js // 2000行のファイル
// 問題点:
// - ファイルが肥大化
// - テストが困難
// - チーム開発が困難

解決: 適切な構造化

// 解決: 機能ごとにモジュール化
my-app/
├── src/
│ ├── modules/
│ │ ├── users/
│ │ └── products/

どの構成を選ぶかは、プロジェクトの規模とチームのニーズによって異なります。プロジェクトの初期段階ではシンプルなMVC構成から始め、必要に応じて徐々に高度な構成に移行していくのが良いでしょう。

シニアエンジニアとして考慮すべき点:

  1. 段階的な進化: 完璧な構成を最初から作ろうとせず、必要に応じて進化させる
  2. チームの合意: 構成はチーム全体で理解し、維持できるものを選ぶ
  3. 一貫性: プロジェクト全体で統一された構成を維持
  4. ドキュメント化: 構成の理由をドキュメント化して、将来のメンテナンスを容易に