React + Django + Firebase でWebアプリケーションを作りたい

# はじめに
## 背景
React、Django、Firebaseを用いてTodoアプリを作っていたのですが、この3つの連携についてほとんどわかっていない状態だったので、調べたことをアウトプットするものになります。扱うFirebaseのサービスは、Firebase Authentication、Firestoreになります。初心者による記事になります。間違い等ありましたら、コメント等いただけると幸いです。

## 対象読者
* ReactとDjangoを軽く触ったことはある方
* それぞれの連携について、よくわかっていない方
* Firebaseの使い方がわからない方

## 概要
* ReactとDjangoの連携について
* Firebase Authenticationの連携
* Firestoreの連携

# ReactとDjangoの連携について
## React側の対応
フロントエンドであるReactからバックエンドであるDjangoに対しリクエストを送り、レスポンスとして受け取った内容を、UIに反映させます。具体的には、**useEffect**というReactのhooksを用います。

:::note info
Reactのhooksは、[公式ドキュメント](https://ja.react.dev/reference/react/hooks)や[こちらの記事](https://qiita.com/seira/items/f063e262b1d57d7e78b4)が参考になります。
:::

### useEffect
useEffectとはコンポーネントを外部システムと同期させるためのhookになります。
具体的な書き方は以下（[参考](https://kinsta.com/jp/knowledgebase/react-useeffect/#2-api)）。
```js
useEffect(() => {
  // 外部APIからデータを取得
  fetch('http://127.0.0.1:8000/hello/')
    .then((response) => response.json())
    .then((data) => {
      // データで何かをする
    });
}, []);
```

:::note info
メソッドやヘッダー情報は、fetchの第2引数にて指定します（[参考](https://stackoverflow.com/questions/75459226/how-to-run-useeffect-in-react-after-post-request)）。
:::

エンドポイントはDjango（バックエンド側）で指定しますので、React側での対応は以上となります。

## Django側の対応
Reactとの連携を行うため、**DjangoRestFramework**を用います。

### エンドポイントを作成する
エンドポイントはurls.pyに記載し、処理はviews.py内に記載します（[参考](https://qiita.com/t-iguchi/items/ac9638dbdbe509515148)）。

例：
```urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('hello/', views.hello_world),
]
```

```views.py
from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(['GET', 'POST']) # 許可するメソッドを指定
def hello_world(request):
    if request.method == 'GET':
        return Response({"message": "Hello, this is a GET request!"})
    elif request.method == 'POST':
        return Response({"message": "Hello, this is a POST request!"})
```

### その他設定事項
CORSの設定や、DRFを有効かする設定をsettings.pyに記述する必要があります。この記事で詳しくは説明しませんが、[こちらの記事](https://qiita.com/studio_haneya/items/8b584fa605b3788d0642)が参考になるかと思います。

# Firebaseの導入
このあたりの実装は、やり方は色々あるかと思います。読者がざっくりイメージできることを目的に執筆します。

## Firebase Authentication
ユーザー認証で用いるサービスになります。
### ①初期化
FirebaseConsoleから得られるFirebaseの情報から、Firebaseの初期化を行います。ここでは、Googleアカウントでの認証を実装しています。
```firebase.js
import { initializeApp } from "firebase/app";
import { getAuth, signInWithPopup, GoogleAuthProvider } from "firebase/auth";

// Firebase設定（FirebaseConsoleから取得）
const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_PROJECT.firebaseapp.com",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_PROJECT.appspot.com",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID",
};

// Firebase初期化
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const provider = new GoogleAuthProvider();

export { auth, provider };
```

### ②ログイン機能（Googleアカウントでのサインインと、IDトークン取得）
実装方法は色々あると思うので、示すものはあくまで一例です。詳細は[こちら](https://zenn.dev/tatsuyasusukida/articles/firebase-auth-email)を見るとよいかと思います。サインイン時に、どのようにReactからDjangoへデータが送られるかイメージできればと思います。なお、**Django側で実装を行わなくても、Firebase Authenticationにユーザー情報は保存されます**。Django側でユーザー情報を使いたい場合に、Django側での実装を行う形になります。
```signin.js
import { signInWithPopup } from "firebase/auth"; // サインインポップアップ表示
import { auth, provider } from "./firebase";

const signInWithGoogle = async () => {
  try {
    const result = await signInWithPopup(auth, provider);
    const user = result.user;
    const token = await user.getIdToken(); // FirebaseのIDトークン取得

    console.log("Firebase Token:", token);

    // Djangoにトークンを送信
    const response = await fetch("http://127.0.0.1:8000/api/auth/", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token}`,
      },
    });

    const data = await response.json();
    console.log("Django API Response:", data);
  } catch (error) {
    console.error("Authentication Error:", error);
  }
}
```

## Firestore
Firebaseで管理できる、NoSQLのDBになります。Firestoreを管理するため、Firebase Admin SDKを使用します。

### ①初期化
FirebaseConsoleから得られるサービスアカウントキーを用いて、Firebaseの初期化を行います。
```settings.py
import firebase_admin
from firebase_admin import credentials

# サービスアカウントの鍵ファイルを指定
cred = credentials.Certificate("path/to/your/serviceAccountKey.json")

# Firebase Adminを初期化
firebase_admin.initialize_app(cred)
```

### ②データ操作
views.pyに記述します。urls.pyでエンドポイントを指定し、フロントエンドとデータのやり取りを行います。
```views.py
from rest_framework.response import Response
from rest_framework.decorators import api_view
from django.http import JsonResponse
from firebase_admin import firestore

# Firestoreクライアントを一度だけ取得（グローバル）
db = firestore.client()

# Firestoreからデータを取得する関数
def get_firestore_data():
    # 'users' コレクションからデータを取得
    users_ref = db.collection('users')
    docs = users_ref.stream()

    return docs

# Firestoreに新しいデータを追加する関数
def add_firestore_data():
    # 'users'コレクションに新しいドキュメントを追加
    doc_ref = db.collection('users').document('new_user')
    doc_ref.set({
        'name': 'John Doe',
        'email': 'johndoe@example.com',
    })

# Firestoreのデータを更新する関数
def update_firestore_data():
    # 特定のドキュメントを取得して更新
    doc_ref = db.collection('users').document('user_id')
    doc_ref.update({
        'email': 'newemail@example.com',
    })

# ユーザー情報をFirestoreに保存するAPI
@api_view(['POST'])
def save_user_to_firestore(request):
    data = request.data
    name = data.get('name')
    email = data.get('email')

    if not name or not email:
        return JsonResponse({"error": "Name and email are required!"}, status=400)

    # 新しいユーザーを'users'コレクションに保存
    doc_ref = db.collection('users').document()  # 新しいドキュメントを作成
    doc_ref.set({
        'name': name,
        'email': email,
    })

    return JsonResponse({"message": "User saved to Firestore successfully!"})

```

:::note info
FirestoreとDjangoの連携でよく使う操作
* データの取得（stream()）: Firestore からドキュメントやコレクションのデータを取得
* データの追加（set()）: 新しいドキュメントをコレクションに追加
* データの更新（update()）: 既存のドキュメントを更新
* データの削除（delete()）: ドキュメントの削除
:::