MinWeb2025のサンプルプロジェクトです。フロントエンドはReact、バックエンドはNode.js (Express)、データベースはMySQLを使用しています。
バックエンドは以下のレイヤーで構成されています:
domain/entities: ビジネスオブジェクトとビジネスルールを定義しますdomain/repositories: リポジトリのインターフェースを定義します
application/usecases: アプリケーションのユースケースを実装します- 各ユースケースは単一の責任を持ちます
 
interfaces/controllers: HTTPリクエストを処理し、適切なユースケースを呼び出します
infrastructure/repositories: リポジトリの具体的な実装(MySQLなど)を提供します
フロントエンドも類似のアーキテクチャを採用しています:
domain/entities: ビジネスオブジェクトを定義します
infrastructure/api: APIとの通信を処理します
presentation/components: UI部品を提供しますpresentation/pages: ページコンポーネントを提供します
- フロントエンド: React, TypeScript
 - バックエンド: Node.js, Express, TypeScript
 - データベース: MySQL
 - 開発/デプロイ: Docker, docker-compose
 
backend: バックエンドサービス (Node.js/Express)frontend: フロントエンドビルド環境 (React)nginx: Webサーバー (静的ファイル配信・リバースプロキシ)mysql: データベースサービス
# 起動
docker-compose up -d
# 停止
docker-compose down
# ボリュームも含めて完全に環境を削除
docker-compose down -vcd frontend
npm install
npm startcd frontend
npm run build- 
src/index.tsx: エントリーポイントimport React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( <React.StrictMode> <App /> </React.StrictMode> );
 - 
src/App.tsx: メインアプリケーションコンポーネントimport React from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { HomePage, TaskListPage, TaskDetailPage } from './presentation/pages'; import { Header, Footer } from './presentation/components/common'; const App: React.FC = () => { return ( <BrowserRouter> <Header /> <main> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/tasks" element={<TaskListPage />} /> <Route path="/tasks/:id" element={<TaskDetailPage />} /> </Routes> </main> <Footer /> </BrowserRouter> ); }; export default App;
 - 
src/domain/entities/: ドメインオブジェクト// src/domain/entities/Task.ts export interface Task { id: number; title: string; description: string; completed: boolean; createdAt: Date; }
 - 
src/infrastructure/api/: API通信処理// src/infrastructure/api/taskApi.ts import { Task } from '../../domain/entities/Task'; const API_BASE_URL = '/api'; export const fetchTasks = async (): Promise<Task[]> => { const response = await fetch(`${API_BASE_URL}/tasks`); if (!response.ok) throw new Error('Failed to fetch tasks'); return response.json(); }; export const fetchTaskById = async (id: number): Promise<Task> => { const response = await fetch(`${API_BASE_URL}/tasks/${id}`); if (!response.ok) throw new Error('Failed to fetch task'); return response.json(); };
 - 
src/presentation/: UI関連のコンポーネント// src/presentation/components/task/TaskItem.tsx import React from 'react'; import { Task } from '../../../domain/entities/Task'; interface TaskItemProps { task: Task; onComplete: (id: number) => void; } export const TaskItem: React.FC<TaskItemProps> = ({ task, onComplete }) => { return ( <div className="task-item"> <h3>{task.title}</h3> <p>{task.description}</p> <span>{task.completed ? '完了' : '未完了'}</span> <button onClick={() => onComplete(task.id)}>完了にする</button> </div> ); };
 
cd backend
npm install
npm run devcd backend
npm run build- 
src/index.ts: エントリーポイントimport express from 'express'; import cors from 'cors'; import taskRoutes from './routes/taskRoutes'; const app = express(); const PORT = process.env.PORT || 3000; app.use(cors()); app.use(express.json()); // ルートの設定 app.use('/api/tasks', taskRoutes); app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
 - 
src/routes/: APIルート定義// src/routes/taskRoutes.ts import { Router } from 'express'; import { TaskController } from '../interfaces/controllers/TaskController'; const router = Router(); const taskController = new TaskController(); router.get('/', taskController.getAllTasks); router.get('/:id', taskController.getTaskById); router.post('/', taskController.createTask); router.put('/:id', taskController.updateTask); router.delete('/:id', taskController.deleteTask); export default router;
 - 
src/domain/: ドメイン層(エンティティ、リポジトリインターフェース)// src/domain/entities/Task.ts export interface Task { id?: number; title: string; description: string; completed: boolean; createdAt?: Date; } // src/domain/repositories/TaskRepository.ts import { Task } from '../entities/Task'; export interface TaskRepository { findAll(): Promise<Task[]>; findById(id: number): Promise<Task | null>; create(task: Task): Promise<Task>; update(id: number, task: Partial<Task>): Promise<Task | null>; delete(id: number): Promise<boolean>; }
 - 
src/application/: アプリケーション層(ユースケース)// src/application/usecases/task/GetAllTasksUseCase.ts import { Task } from '../../../domain/entities/Task'; import { TaskRepository } from '../../../domain/repositories/TaskRepository'; export class GetAllTasksUseCase { constructor(private taskRepository: TaskRepository) {} async execute(): Promise<Task[]> { return this.taskRepository.findAll(); } }
 - 
src/interfaces/: インターフェース層(コントローラー)// src/interfaces/controllers/TaskController.ts import { Request, Response } from 'express'; import { GetAllTasksUseCase } from '../../application/usecases/task/GetAllTasksUseCase'; import { GetTaskByIdUseCase } from '../../application/usecases/task/GetTaskByIdUseCase'; import { MySQLTaskRepository } from '../../infrastructure/repositories/MySQLTaskRepository'; const taskRepository = new MySQLTaskRepository(); export class TaskController { async getAllTasks(req: Request, res: Response): Promise<void> { try { const useCase = new GetAllTasksUseCase(taskRepository); const tasks = await useCase.execute(); res.json(tasks); } catch (error) { res.status(500).json({ error: 'Internal Server Error' }); } } // 他のコントローラーメソッド... }
 - 
src/infrastructure/: インフラストラクチャ層(リポジトリ実装など)// src/infrastructure/repositories/MySQLTaskRepository.ts import { Task } from '../../domain/entities/Task'; import { TaskRepository } from '../../domain/repositories/TaskRepository'; import { pool } from '../database/mysql'; export class MySQLTaskRepository implements TaskRepository { async findAll(): Promise<Task[]> { const [rows] = await pool.query('SELECT * FROM tasks'); return rows as Task[]; } async findById(id: number): Promise<Task | null> { const [rows] = await pool.query('SELECT * FROM tasks WHERE id = ?', [id]); const tasks = rows as Task[]; return tasks.length > 0 ? tasks[0] : null; } // 他のリポジトリメソッド... }
 
データベースの初期化スクリプトは mysql/init/ ディレクトリに配置されています。
- ホスト:  
mysql(コンテナ内から) - ユーザー: 
user - パスワード: 
password - データベース名: 
minweb - ポート: 
3306