AI駆動開発で作る旅行記録SNS「JP TravelMap」— 設計・インフラ構築の全記録
はじめに
Qiitaの記事でAI駆動開発に興味を持ち、「自分でも何か作ってみたい」と思ったのがきっかけです。
旅行は好きだけど、旅先を決めるのがいつも億劫でした。誰かのリアルな旅行記録を地図上で見られたら旅先選びに役立つのでは、と考えて作ったのが JP TravelMap です。
技術スタック
| 領域 | 使用技術 |
|---|---|
| バックエンド | Laravel 12 + PHP 8.5 |
| フロントエンド | React 19 + Vite |
| 認証 | Laravel Sanctum + Google OAuth |
| DB | MySQL(AWS RDS) |
| インフラ | AWS EC2 + RDS + Route53 |
| Webサーバー | Nginx + PHP-FPM |
| SSL | Let's Encrypt(自動更新) |
| 開発手法 | AI駆動開発(Antigravity) |
機能一覧
- 旅行投稿(タイトル・説明・都道府県・市区町村・複数写真)
- 日本地図から都道府県をクリックして投稿を閲覧
- いいね・フォロー
- 公開範囲設定(全体公開 / フォロワーのみ / 非公開)
- ユーザー検索
- Google OAuthログイン
- メール認証
DB設計
users
posts ← user_id, city_id(FK)
photos ← post_id(FK)
photo_groups ← post_id(FK)複数写真をグループ管理
prefectures ← capital_city_id(県庁所在地、地図の中心点として使用)
cities ← prefecture_id, latitude, longitude
likes ← post_id, user_id
comments ← post_id, user_id
follows ← follower_id, following_id
prefecture_favorite_photos ← 都道府県ごとの代表写真
地図機能の実現のため、citiesテーブルに緯度経度を持たせ、prefecturesにcapital_city_idを設けて都道府県単位での中心座標を計算できるようにしました。
設計で工夫した点
1. 写真のグループ管理
1投稿に複数の写真をアップロードでき、写真の並び替えもできます。photo_groupsテーブルで論理的なグループを作り、photosはグループ内のdisplay_orderで順番を管理します。
2. 日本地図とDBの連携
GeoJSONで都道府県の境界データを描画し、クリックした都道府県IDをもとにDBの投稿を絞り込みます。prefecturesテーブルのcapital_city_idを使って地図上のピンの初期位置を計算しました。
3. N+1問題の解消
初期実装ではAPIが1投稿ごとにいいね数を都度クエリしており、30件の投稿一覧で60本以上のクエリが走っていました。withCount()とwith()を使ったEager Loadingに書き直し、クエリ数を大幅に削減しました。
// Before(N+1)
$post->likes()->count(); // 投稿ごとに毎回クエリ
// After
Post::withCount('likes')->with('likes')->get();
$post->likes_count; // 追加クエリなし
4. 共通APIクライアント
フロントエンドの各コンポーネントでlocalStorage.getItem('token')と認証ヘッダーの組み立てを繰り返していたのを、apiFetch()関数に集約しました。
// frontend/src/api.js
export function apiFetch(path, options = {}) {
const token = localStorage.getItem('token');
const headers = { ...options.headers };
if (token) headers['Authorization'] = `Bearer ${token}`;
return fetch(`/api${path}`, { ...options, headers });
}
インフラ構成
ユーザー
↓
Route53(DNS)
↓
EC2(Nginx + PHP-FPM + React dist)
↓
RDS(MySQL)
EC2とRDSを分離することで、EC2を再起動してもDBのデータが消えない構成にしています。
Nginx構成のポイント
SPAとLaravel APIを1台のEC2・1つのNginxで同居させています。
/api, /auth, /storage → Laravel(PHP-FPM)
location ~ ^/(api|auth|email|sanctum|storage) {
root /var/www/travelmap/backend/public;
try_files $uri $uri/ /index.php?$query_string;
}
それ以外 → React SPA
location / {
try_files $uri $uri/ /index.html;
}
セキュリティ設定
本番環境で設定した主なセキュリティ項目です。
APP_DEBUG=false # エラー詳細を非公開
server_tokens off # NginxのVersionを非公開
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000
苦労した点
設計の甘さと仕様変更の連鎖
開発を進めるうちに「あの機能も欲しい」「この情報も持たせたい」という追加要望が次々と出てきました。
自分が自分のクライアントなので歯止めが利かず、DBスキーマの変更・マイグレーションの追加が繰り返し発生しました。
実際に後から追加になった主なもの:
- 写真のグループ管理(最初は1投稿1枚想定だった)
- 公開範囲設定(visibility カラムの追加)
- フォロー機能
- 都道府県の代表写真(
prefecture_favorite_photosテーブル) - 緯度経度カラム(地図機能を後から強化)
最初にER図を書いて「これで完成」と思っていても、画面を作り始めると「やっぱりこのデータも必要だった」が連発します。最初の設計フェーズにもっと時間をかけ、画面設計とDBスキーマを往復しながら詰めておくべきでした。初めてのアプリ開発で身をもって学んだ教訓です。
まとめと今後
初めてのアプリ開発でしたが、AI駆動開発のおかげで1人でも設計からインフラまで通しで作ることができました。
今後追加したい機能:
プッシュ通知
旅行プランの共有機能
投稿のJSバンドル分割(現在756KB)
有識者たち方からのフィードバックをお待ちしています。特に設計ではどういうツールを使えばいいか全くわからず、画面遷移図などはFigmaで書いていました。
インフラ面でも「こうすればよかった」という点があればぜひコメントください。
