0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React+Firebase】旅の支出を多通貨で管理するアプリ「Tabi Money」を作ってみた.3

Last updated at Posted at 2026-01-27

【第3回】Firebase AuthenticationとFirestoreの「親子関係」設計

前回のReact実装編に続き、今回はバックエンド(Firebase)の設定と、データの持ち方について。

1. Firebase Authentication(Googleログイン)

ユーザーごとにデータを管理するため、Googleログインを導入しました。ここでは「認証状態をどう監視し、画面をどう切り替えるか」がポイントです。

スクリーンショット 2026-01-27 18.18.22.png

認証状態のリアルタイム監視

onAuthStateChanged を使用して、ユーザーのログイン・ログアウトを常に監視しています。

src/app.jsx
function App() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // ログイン状態が変わるたびに実行される「監視役」
    const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
      setUser(currentUser);
      setLoading(false);
    });
    // コンポーネントが消える時に監視を止める(メモリリーク防止)
    return () => unsubscribe();
  }, []);

  return (
    <Router>
      <Navbar user={user} />
      <Routes>
        {/* ユーザーが存在すればHome、いなければLoginを表示(条件付きレンダリング) */}
        <Route path="/" element={user ? <Home user={user} /> : <Login />} />
        {/* ...他のルート */}
      </Routes>
    </Router>
  );
}

自分だけのデータを取得する

データを保存する際に uid: auth.currentUser.uid を付与し、取得時にはそのIDでフィルタリング(where句)することで、他人のデータが混ざらないようにしています。

/src/components/Home.jsx
const q = query(
  collection(db, "trips"),
  where("uid", "==", auth.currentUser.uid), // 自分のIDと一致するものだけ!
  orderBy("createdAt", "desc")
);


2. Firestoreのデータ構造(親子関係:サブコレクション)

支出データ(transactions)を管理する際、「どの支出が、どの旅行に関連するものかを明確にするため、Firestoreの階層構造(サブコレクション)を利用しました。

支出の画面
スクリーンショット 2026-01-26 18.58.42.png

データ構造のイメージ

  • 親 (Parent): trips コレクション(旅行の基本情報)
  • 子 (Child): transactions サブコレクション(その旅行内での支出明細)

サブコレクションへの保存実装

React Routerの useParams で取得した id(旅行プランのドキュメントID)をパスに含めることで、親子関係を構築しています。

src/components/TripDetail.jsx
const addItem = async (newTitle, newPrice, newCategory) => {
  if (!rate) return alert("為替レートを取得中です...");

  const priceInJPY = Math.round(Number(newPrice) / rate);

  try {
    // 保存先の参照:trips -> {現在の旅行ID} -> transactions という階層を作る
    const transactionsRef = collection(db, "trips", id, "transactions");

    await addDoc(transactionsRef, {
      name: newTitle,
      priceLocal: Number(newPrice),
      priceJPY: priceInJPY,
      category: newCategory,
      createdAt: new Date(),
    });
  } catch (e) {
    console.error("保存失敗: ", e);
  }
};

ここの部分のコンポーネントに使用する関数
スクリーンショット 2026-01-26 18.58.11.png

src/components/TripDetail.jsx
          {isModalOpen && (
            // 「項目を追加」のモーダル部分。
            <AddTransactionModal
              setIsModalOpen={setIsModalOpen}
              addItem={addItem}
              categories={categories}
            />
          )}

第3回記事の後半部分ですね!セキュリティとデプロイは、アプリを「個人の練習台」から「社会に出せるサービス」へと引き上げる重要なステップです。

Qiitaで読者がそのまま真似できるように、具体的なルールコードを載せつつ、エンジニアらしい「なぜこれが必要か」という視点を補強して整えました。


3. セキュリティルール(Firestore Rules)

FirebaseはAPIキーがフロントエンドに露出するため、それだけでは誰でもデータベースを操作できてしまうリスクがあります。その「最後の砦」となるのがセキュリティルールです。
スクリーンショット 2026-01-27 17.52.40.png

「自分以外のデータは絶対に見せない」鉄壁の守り

Firestoreのルールタブを以下のように書き換えることで、ログインユーザー本人以外のアクセスを完全に遮断しました。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // tripsコレクションのルール
    match /trips/{tripId} {
      // データのuidと、操作しようとしている人のuidが一致する場合のみ許可
      allow read, update, delete: if request.auth != null && request.auth.uid == resource.data.uid;
      // 作成はログインしていればOK
      allow create: if request.auth != null;

      // 支出(transactions)サブコレクションも親ドキュメントの所有者のみ
      match /transactions/{transactionId} {
        allow read, write: if request.auth != null && 
          get(/databases/$(database)/documents/trips/$(tripId)).data.uid == request.auth.uid;
      }
    }
  }
}

ここで使っている resource.data.uid は、DB内に保存したデータの持ち主。一方 request.auth.uid は、今アクセスしている本人のIDです。この一行が一致しない限り、たとえAPIキーが知られてもデータが盗まれることはありません。


4. Firebase Hosting で世界へ公開(デプロイ)

最後は、作成したアプリを世界中からアクセス可能なURLとして発行します。Firebase Hostingを利用することで、インフラ管理を意識せずスムーズに公開できました。

デプロイの手順

  1. Firebase Tools のインストール
    CLIツールをインストールして、PCからFirebaseを操作できるようにします。
npm install -g firebase-tools
  1. 初期化設定(firebase init
    プロジェクトの紐付けを行います。Viteを使用している場合、公開ディレクトリの設定には注意が必要です。
  • Public directory: dist(Viteのデフォルト出力先)
  • Single-page app: Yes(React Routerを正しく動作させるために必須)
  1. 本番用ビルドとデプロイ
    コードを最適化された静的ファイルに変換し、サーバーへアップロードします。
npm run build
firebase deploy

おわりに

5日間という短期間でしたが、Reactの基礎からFirebaseによるバックエンド構築、そしてデプロイまでを一通り経験することができました。

「自分で課題を見つけ、それを解決するためのツールを技術で形にする」というプロセスは、4月からエンジニアとして働く上で大きな自信になりました。

今後はこのアプリを実際に旅行で使いつつ、さらなるスキルアップに励みたいと思います!

実際に公開してるサイトはこちら!:https://tabimoney-64c36.web.app/

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?