システム構成図
全体アーキテクチャ
┌─────────────────────────────────────────────────────────┐
│ ユーザー (ブラウザ / PWA) │
└────────────────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ React + Vite (SPA) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 地図表示 │ │ 検索UI │ │ プラン管理│ │ ダッシュ │ │
│ │ (Leaflet)│ │ │ │ (D&D) │ │ ボード │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ Zustand (状態管理) │
└────────────────────────────┬────────────────────────────┘
│ REST API
▼
┌─────────────────────────────────────────────────────────┐
│ Node.js + Express │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ 認証 │ │ マイル検索 │ │ プラン管理│ │
│ │ (JWT) │ │ サービス │ │ サービス │ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ ┌───────────┐ ┌───────────┐ │
│ │ 宿泊検索 │ │ レコメンド │ │
│ │ サービス │ │ エンジン │ │
│ └─────┬─────┘ └───────────┘ │
│ │ │
│ ┌─────▼─────────────────────────────────┐ │
│ │ SQLite (better-sqlite3) │ │
│ │ マイルチャート / 空港 / ユーザー / プラン │ │
│ └───────────────────────────────────────┘ │
└────────────────────────────┬────────────────────────────┘
│ 外部API呼び出し
▼
┌─────────────────────────────────────────────────────────┐
│ 外部サービス │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │楽天トラベル│ │ agoda │ │ trip.com │ │booking │ │
│ │ API │ │ API │ │ API │ │.com API │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘データフロー
マイル検索フロー
ユーザー入力(所持マイル、出発空港、日程)
│
▼
フロントエンド: 検索パラメータのバリデーション
│
▼
API: GET /api/v1/search/destinations
│
├── マイルチャートDB から候補路線を抽出
│ (所持マイル ≥ 必要マイル数)
│
├── シーズン判定(日程 → L/R/H)
│
├── 各目的地の宿泊情報を並行取得
│ ├── 楽天トラベル API
│ ├── agoda API
│ ├── trip.com API
│ └── booking.com API
│
▼
レスポンス: 目的地リスト
├── 必要マイル数
├── 航空券の通常価格(マイル効率算出用)
├── 宿泊最安値(4社比較済み)
└── 総コスト概算逆引きレコメンドフロー
ユーザー入力(予算上限、日数、出発空港)
│
▼
API: GET /api/v1/search/recommend
│
├── 全路線から候補を抽出
│
├── 各候補にスコアリング
│ ├── マイル効率 = 通常価格 ÷ 必要マイル
│ ├── 宿泊コスパ = 最安値 ÷ 評価
│ └── 総合スコア = 加重平均
│
▼
レスポンス: ランキング形式のレコメンドリストデプロイ構成
Docker(セルフホスト)
yaml
# docker-compose.yml
services:
app:
image: miletrek:latest
ports:
- "3000:3000"
volumes:
- ./data:/app/data # SQLite DB
- ./uploads:/app/uploads # ユーザーアップロード
environment:
- NODE_ENV=production
- JWT_SECRET=${JWT_SECRET}
restart: unless-stoppedポート構成
| ポート | 用途 |
|---|---|
| 3000 | Express (API + SPA配信) |
Express が Vite でビルドされた SPA の静的ファイルも配信する(単一ポート構成)。