🔰基礎技術

Firestoreなら「本棚」感覚でデータを保存できる。Next.jsでの読み書き・リアルタイム更新の実装手順

前回の記事で「ログイン機能」を作り、アプリに鍵をかけました。
しかし、今のままではログインしても「何も書けない真っ白なノート」です。

今回は、ユーザーがメモやデータを記録するためのデータベース「Cloud Firestore(ファイアストア)」を導入します。

「データベースって、SQLとか難しい言語を覚えるんでしょ?」
そう思ったあなたこそ、Firestoreを使うべきです。これは、難しい理論を抜きにして「直感」で扱える、初心者にも比較的優しいデータベースです。

1. Firestoreとは? イメージは「巨大な書庫」

Firestoreは、Googleが管理するNoSQL(ノーエスキューエル)データベースです。
従来のデータベース(Excelのような表形式)とは違い、以下のような構造になっています。

  • コレクション(本棚): 「メモ」「ユーザー」などのカテゴリ。
  • ドキュメント(1冊の本): データのまとまり。背表紙にIDが付いています。
  • フィールド(本の内容): 実際のデータ。「タイトル:買い物」「本文:卵を買う」など。

「本棚(memos)」から「特定の本(memo-001)」を取り出して読む。
このシンプルな感覚で操作できるのが最大の特徴です。

2. なぜ初心者にFirestoreなのか?

世の中にはMySQLやPostgreSQLなど有名なデータベースがありますが、個人開発デビューにはFirestoreを強くおすすめします。

① データの持ち方が直感的でわかりやすい

PostgreSQLなどのデータベースはデータを取得・更新などのデータ操作するためにSQLという言語の習得が必要です。
Firestoreは、どの本棚からどの本を取り出すかなどの指定が直感的でわかりやすくなっています。

② 管理画面が見やすい

データがどのように保存されているか、Googleの管理画面でそのまま視覚的に確認・編集できます。「黒い画面でコマンドを打って確認」なんて苦行は必要ありません。

③ 類似サービスとの比較

サービス名特徴初心者おすすめ度
FirestoreGoogle純正。リアルタイム同期が標準装備。◎(最高)
Supabase最近人気。RDB(Postgres)が使えるが設計知識が必要。◯(中級者向)
AWS DynamoDB超高性能だが、設定項目が多く難解。△(玄人向)

☕ コラム:RDB(リレーショナルデータベース)とは何が違う?

初心者が混乱しやすい「RDB(MySQLなど)」と「NoSQL(Firestore)」の違いを整理しましょう。

  • RDB(Excelのイメージ):
    「列」と「行」が決まっています。「名前」の列に数字を入れたり、勝手に列を増やしたりできません。カッチリ管理したい業務システム向き。
  • NoSQL(自由帳のイメージ):
    決まった型がありません。1ページ目には文字だけ、2ページ目には絵を描く、といった自由な保存ができます。変更が多いアプリ開発向き。

3. 【準備】Firebase側でのデータベース作成

では、本棚(データベース)を用意しましょう。

  1. Firebaseコンソールの左メニューから「Firestore Database」をクリック。
  2. 「データベースの作成」ボタンを押す。
  3. ロケーション(場所)を選択。
    ※日本からのアクセスが主なら asia-northeast1 (東京) が速いです。
  4. セキュリティルールの設定。
    今回は開発用なので「テストモードで開始する」を選んで「作成」を押してください。
    ※テストモードは「誰でも読み書きOK」の状態です。30日後に使えなくなるので、開発が進んだら設定変更が必要です。

4. 【実装】Next.jsでメモアプリを作る

今回は「ログインした人だけが見れるメモ帳」を作ります。
前回のコードに継ぎ足していきます。

① lib/firebase.ts の更新

まずはアプリからデータベースを使えるようにします。前回作ったファイルに、getFirestore の行を追加するだけです。

// lib/firebase.ts
import { initializeApp } from"firebase/app";
import { getAuth } from"firebase/auth";
// ↓【追加】データベース機能のインポート
import { getFirestore } from"firebase/firestore";

const firebaseConfig = {
/* ...前回の設定のまま... */
};

const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
// ↓【追加】データベースをエクスポート
export const db = getFirestore(app);

② メモ機能コンポーネントの作成

app/components/MemoApp.tsx を新規作成します。
これが「登録」「一覧表示」「削除」を行うメイン画面です。

// app/components/MemoApp.tsx
"use client";

import { useState, useEffect } from"react";
import { db } from"@/lib/firebase";
// Firestoreを操作するための関数たち
import { collection, addDoc, onSnapshot, deleteDoc, doc, query, orderBy } from"firebase/firestore";

export default function MemoApp() {
const [memos, setMemos] = useState<any[]>([]);
const [inputValue, setInputValue] = useState("");

// 1. リアルタイムでデータを取得する(魔法のコード)
  useEffect(() => {
// "memos"という本棚を見張る
const q = query(collection(db, "memos"), orderBy("createdAt", "desc"));
const unsubscribe = onSnapshot(q, (snapshot) => {
// データが変わるたびにここが実行される
const newMemos = snapshot.docs.map((doc) => ({
        id: doc.id, ...doc.data()
      }));
      setMemos(newMemos);
    });
return () => unsubscribe();
  }, []);

// 2. データを追加する
const addMemo = async () => {
if (!inputValue) return;
await addDoc(collection(db, "memos"), {
      text: inputValue,
      createdAt: new Date(),
    });
    setInputValue("");
  };

// 3. データを削除する
const deleteMemo = async (id: string) => {
await deleteDoc(doc(db, "memos", id));
  };

return (
    <div style={{ maxWidth: "500px", margin: "0 auto" }}>
      <div style={{ display: "flex", gap: "10px", marginBottom: "20px" }}>
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="メモを入力..."
          style={{ flex: 1, padding: "8px" }}
        />
        <button onClick={addMemo} style={{ padding: "8px 16px" }}>追加</button>
      </div>

      <ul style={{ listStyle: "none", padding: 0 }}>
        {memos.map((memo) => (
          <li key={memo.id} style={{ borderBottom: "1px solid #ccc", padding: "10px", display: "flex", justifyContent: "space-between" }}>
            <span>{memo.text}</span>
            <button onClick={() => deleteMemo(memo.id)} style={{ color: "red" }}>削除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

③ トップページに組み込む

最後に、app/page.tsx を編集します。
「ログインしている時だけメモアプリを表示し、していない時はログインボタンを表示する」という分岐を作ります。

// app/page.tsx
"use client";
import { useEffect, useState } from"react";
import { auth } from"@/lib/firebase";
import AuthButton from"./components/AuthButton";
import MemoApp from"./components/MemoApp"; // インポート

export default function Home() {
const [user, setUser] = useState<any>(null);

  useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((u) => setUser(u));
return () => unsubscribe();
  }, []);

return (
    <main style={{ padding: "50px", textAlign: "center" }}>
      <h1>Simple Memo App</h1>
      <div style={{ margin: "30px 0" }}>
        <AuthButton />
      </div>

      {/* ログインしている時だけメモアプリを表示 */}
      {user && <MemoApp />}
    </main>
  );
}

5. 動作確認(テスト)

ターミナルで npm run dev を実行し、ブラウザを開いてみましょう。

  1. ログインボタンを押してログイン。
  2. 入力欄が出るので、何か入力して「追加」を押す。
  3. 【感動ポイント】 別のウィンドウ(またはスマホ)で同じURLを開いてみてください。片方で追加すると、もう片方の画面にも一瞬でメモが出現します。

⚠️ 開発環境と本番環境について

今は手元のPC(ローカル環境)から、ネット上のFirebase本番サーバーに繋いでいます。
実務では「開発用のFirebaseプロジェクト」と「本番用のFirebaseプロジェクト」を分けて作ることが一般的ですが、個人開発の最初は「ローカルも本番も同じFirebaseを見る」形で進めても問題ありません。まずは動く喜びを知りましょう!

まとめ:アプリに「記憶」が宿った

おめでとうございます!これであなたのアプリは、ユーザーを識別し、そのデータを保存・共有できるようになりました。
Firestoreの「リアルタイム同期」を体験すると、もう普通のデータベースには戻れないはずです。

しかし、まだこのアプリはあなたのPCの中にしかありません。
次回は、いよいよこれをインターネット上の住所(ドメイン)に公開し、世界中の人がアクセスできるようにします。

👉 次のステップ:[次回記事:Vercelで独自ドメイン公開。お名前.comで買ったURLを紐付ける手順]

-🔰基礎技術