ディレクトリ構成 ~フィーチャーベース編~

Kodai Nakahara

2025.5.28

はじめに

アプリケーション開発において、ディレクトリ構成は保守性・拡張性・開発効率に直結する設計要素です。

本記事では、以下のような課題に悩む現場に向けて、「機能ごとに整理しやすく、拡張にも強い」フィーチャーベース構成をご紹介します。

  • プロジェクトが大きくなるとファイルが散乱し、管理しにくくなる
  • 新規メンバーが「どこに何があるか」迷いやすい
  • 小さく始めた構成が、成長とともに破綻しやすい

⚠️ この構成が「唯一の正解」ではありません。

本記事は、筆者が実案件・チーム開発の中で得た知見をもとに「比較的扱いやすく、現実的に運用しやすい」と感じた構成パターンの一例です。

チームの人数・技術レベル・プロジェクトの性質に応じて、適宜カスタマイズして取り入れてください。

構成方針

  • 機能(feature)単位でディレクトリを分ける
  • 各機能内はファイル単位で責務を整理(components.tsx, usecases.ts など)
  • 複数の feature を組み合わせる処理(画面UI/レスポンス)は外側で組む
  • 共通処理は shared/、アプリ基盤は core/ に配置

features の分け方の基準

「何を1つの feature と見なすか」は、構成全体の見通しと保守性に大きく影響します。以下のような基準を参考に設計しましょう。

分類基準内容
業務ドメイン単位例:user, product, order, dashboard
画面単位責務が明確な単一画面(例:Login, Profile, Settings
API単位REST/GraphQL のエンドポイント構成に準拠
変更頻度・チーム単位頻繁に改修が入る機能や、担当チームごとに分ける場合も有効

「何を1つの feature とするか」は、構成全体の可読性と拡張性を左右します。以下のような視点で切り分けるのがおすすめです。

💡 基本は「1つの機能で完結する画面・ロジック・状態」をひとまとめに。

境界が曖昧な場合は最初は大まかに分け、必要に応じて分割・統合していくのが現実的です。

具体例

  • login, signup, forgotPassword は最初は auth/ にまとめる
    → 認証系の UI・API・状態管理を集約して管理しやすくなる
  • user, profile, settings などユーザー周辺機能は
    → 当初は user/ 配下に一緒に置いても良い
    → 将来的に profile/settings/ を独立して切り出してもOK

ディレクトリ構成例

フロントエンド

src/
├── features/                       // 機能単位でまとめる(UI・ロジック・状態)
│   ├── user/
│   │   ├── components.tsx          // ユーザー機能専用の UI コンポーネント
│   │   ├── hooks.ts                // カスタムフック
│   │   ├── usecases.ts             // ユースケース(UIから呼び出す操作単位)
│   │   ├── repository.ts           // データ取得処理(API, localStorageなど)
│   │   └── store.ts                // Zustand, Redux などの状態管理
│   ├── product/
│   └── order/
│
├── pages/                          // 複数 feature を組み合わせて画面を構築
│   ├── dashboard.tsx
│   └── user.tsx
│
├── shared/                         // 再利用可能な UI / ユーティリティ群
│   ├── components.tsx              // 共通UI(Button, Modalなど)
│   ├── utils.ts                    // 汎用関数(純関数)
│   ├── constants.ts                // 定数
│   └── types.ts                    // 共通型
│
├── core/                           // アプリ全体の基盤機能
│   ├── api.ts                      // API クライアント設定(axios等)
│   ├── auth.ts                     // 認証関連処理
│   ├── config.ts                   // 環境設定
│   └── store.ts                    // グローバルストアの初期化
│
└── main.tsx                        // エントリーポイント
  • 各 feature の components.tsx に、機能固有の UI を定義
  • 状態管理・API ロジックは store.ts / hooks.ts / usecases.ts に記述
  • それらを pages/自由に組み合わせて画面を構成

💡設計のポイント

  • features/* では 状態・ロジック・UI を完結させる
  • 複数機能の組み合わせて pages/ にまとめて 画面UI を構築
  • 共通部品・定数・型shared/ に切り出し DRY を意識
  • アプリ全体の設定や状態管理は core/ に集約

UI の組み立て:(単一 feature のみで構成される例)

// pages/user.tsx
import { UserList } from '@/features/user/components';
import { useUser } from '@/features/user/hooks';

export const UserPage = () => {
  const { users } = useUser();
  return <UserList users={users} />;
};

UI の組み立て(複数 feature で構成される例)

// pages/dashboard.tsx
import { UserCard } from '@/features/user/components';
import { ProductCard } from '@/features/product/components';
import { useUser } from '@/features/user/hooks';
import { useProduct } from '@/features/product/hooks';

export const Dashboard = () => {
  const { currentUser } = useUser();
  const { featuredProduct } = useProduct();

  return (
    <>
      <UserCard name={currentUser.name} />
      <ProductCard product={featuredProduct} />
    </>
  );
};

バックエンド

src/
├── features/                       // 各機能のドメインロジック
│   ├── user/
│   │   ├── usecases.ts             // ユースケース(アプリケーションサービス層)
│   │   ├── repository.ts           // DBや外部APIアクセス
│   │   └── types.ts                // 型定義
│   ├── product/
│   └── order/
│
├── routes/                         // エンドポイント単位のルーティング(レスポンス構成)
│   ├── user/route.ts               // /api/users
│   └── dashboard/route.ts          // /api/dashboard(複数featureの合成)
│
├── shared/                         // 共通型・定数・関数
│   ├── utils.ts
│   ├── constants.ts
│   └── types.ts
│
├── core/                           // アプリ全体の初期化・共通処理
│   ├── config.ts
│   ├── database.ts
│   ├── auth.ts
│   └── errorHandler.ts
│
└── main.ts                         // エントリーポイント

💡設計のポイント

  • features/*ロジックとデータ取得に専念(サービス・DB)
  • エンドポイント処理は routes/*/route.ts に集約
  • レスポンス構造は自由に構成できるよう、戻り値の組み合わせで柔軟に設計
  • すべてのルートは main.ts で明示的にマウントし、アプリの起点を一元管理

APIの組み立て(単一 feature のみで構成される例)

// routes/user/route.ts
import { Router } from 'express';
import { getUserById } from '@/features/user/usecase';

const router = Router();

router.get('/:userId', async (req, res) => {
  const user = await getUserById(req.params.userId);
  res.json(user);
});

export default router;

APIの組み立て(複数 feature のみで構成される例)

// routes/dashboard/route.ts
import { Router } from 'express';
import { getUserById } from '@/features/user/usecase';
import { getOrdersByUserId } from '@/features/order/usecase';
import { getProductsByIds } from '@/features/product/usecase';

const router = Router();

router.get('/:userId', async (req, res) => {
  const userId = req.params.userId;

  const user = await getUserById(userId);
  const orders = await getOrdersByUserId(userId);
  const productIds = orders.flatMap((o) => o.productIds);
  const products = await getProductsByIds(productIds);

  res.json({
    user: {
      name: user.name,
      email: user.email,
    },
    orderSummary: {
      count: orders.length,
      products,
    },
  });
});

export default router;

ルーティングの結合例

// main.ts
import express from 'express';
import dashboardRoute from './routes/dashboard/route';
import userRoute from './routes/user/route';

const app = express();
app.use(express.json());

app.use('/api/dashboard', dashboardRoute);
app.use('/api/users', userRoute);

app.listen(3000, () => {
  console.log('Server is running on <http://localhost:3000>');
});

おわりに

この構成は、以下のような開発スタイルにフィットします:

  • 複数人・複数機能で並行して開発するチーム
  • 小さく始めて段階的に機能追加していきたいプロジェクト
  • 機能ごとに責務を分離し、変更しやすく保守しやすい設計を求める現場

構成は一度決めたら終わりではなく、育てていくもの。

現在の最適解が未来のボトルネックにならないよう、シンプルな原則を軸に柔軟な見直しを続けていきましょう。

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ