FirestoreのCRUD操作をTypeScriptで共通化する

# はじめに

こんにちは、Gakken LEAP のエンジニアの Minami です。
WEBアプリケーションを開発するにあたり、DBとしてFirestoreを使用しました。初めての使用だったこともあり、迷ったところもあったので備忘録的な意味も込めてまとめてみました。

---

# Firestoreとは

Firestoreは、Googleが提供するNoSQLクラウドデータベースで、Firebaseプラットフォームの一部として利用できます。リアルタイムでデータの同期が可能で、モバイルやウェブアプリケーション向けに設計されています。Firestoreは、次のような特徴を持っています。

- **スケーラビリティ**: Firestoreはスケールしやすく、アプリの成長に応じて柔軟に拡張できます。
- **リアルタイム同期**: Firestoreを使用すると、データが変更されるたびにリアルタイムでデバイス間でデータを同期できます。
- **構造化されたデータ**: Firestoreはドキュメントベースのデータベースで、データを階層構造で保存できます（ドキュメントがコレクション内に保存される）。
- **トランザクションとバッチ書き込み**: 複数のドキュメントに対して一貫性のある更新を行うことができます。

Firestoreを使用することで、アプリケーションのデータの管理が非常に簡単になり、特にリアルタイムアプリや大規模なスケールが必要なアプリに適しています。

---

# Firebaseの設定

まずはFirebaseの設定を行います。

```typescript
import { initializeApp, getApps, getApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

// Firebaseの設定（セキュリティのためマスクしています）
const firebaseConfig = {
  apiKey: "************",
  authDomain: "************",
  projectId: "************",
  storageBucket: "************",
  messagingSenderId: "************",
  appId: "************",
  measurementId: "************"
};

const app = !getApps().length ? initializeApp(firebaseConfig) : getApp();
const db = getFirestore(app);

export { db };
```

---

# ドキュメントを追加する関数(Create)

Firestoreのコレクションに新しいドキュメントを追加するための関数です。

```typescript
import { db } from "./firebase";
import { addDoc, collection } from "firebase/firestore";

export const addDocument = async (collectionName: string, data: any) => {
  try {
    const docRef = await addDoc(collection(db, collectionName), data);
    console.log("Document written with ID:", docRef.id);
    return docRef.id;
  } catch (error) {
    console.error("Error adding document:", error);
    throw error;
  }
};
```

---

# 全てのドキュメントを取得する関数(Read)

指定したコレクション内の全てのドキュメントを取得する関数です。型を汎用的に扱えるようにジェネリック型を使っています。

```typescript
import { db } from "./firebase";
import { getDocs, collection } from "firebase/firestore";

export const getAllDocuments = async <T extends { id: string }>(collectionName: string): Promise<T[]> => {
  try {
    const querySnapshot = await getDocs(collection(db, collectionName));
    const documents = querySnapshot.docs.map(doc => {
      const data = doc.data() as Omit<T, "id">;
      return { id: doc.id, ...data } as T;
    });
    return documents;
  } catch (error) {
    console.error("Error getting documents:", error);
    throw error;
  }
};
```

---

# 特定のドキュメントを取得する関数(Read)

指定したコレクションから特定のIDのドキュメントを取得する関数です。

```typescript
import { db } from "./firebase";
import { getDoc, doc } from "firebase/firestore";

export const getDocument = async (collectionName: string, docId: string) => {
  try {
    const docRef = doc(db, collectionName, docId);
    const docSnap = await getDoc(docRef);

    if (docSnap.exists()) {
      console.log("Document data:", docSnap.data());
      return docSnap.data();
    } else {
      console.log("No such document!");
      return null;
    }
  } catch (error) {
    console.error("Error getting document:", error);
    throw error;
  }
};
```

---

# ドキュメントを更新する関数(Update)

既存のドキュメントを更新するための関数です。指定したデータを上書きします。

```typescript
import { db } from "./firebase";
import { updateDoc, doc } from "firebase/firestore";

export const updateDocument = async (collectionName: string, docId: string, data: any) => {
  try {
    const docRef = doc(db, collectionName, docId);
    await updateDoc(docRef, data);
    console.log("Document successfully updated!");
  } catch (error) {
    console.error("Error updating document:", error);
    throw error;
  }
};
```

---

# ドキュメントを削除する関数(Delete)

Firestoreのコレクションから特定のドキュメントを削除するための関数です。

```typescript
import { db } from "./firebase";
import { deleteDoc, doc } from "firebase/firestore";

export const deleteDocument = async (collectionName: string, docId: string) => {
  try {
    const docRef = doc(db, collectionName, docId);
    await deleteDoc(docRef);
    console.log("Document successfully deleted!");
  } catch (error) {
    console.error("Error deleting document:", error);
    throw error;
  }
};

```

---

# おわりに

これで、Firestoreを使った基本的なCRUD操作をTypeScriptで共通化することができました。
Firestoreはリアルタイム同期やスケーラビリティなど、モダンなアプリ開発にピッタリなデータベースです。今回まとめた関数は、どんなアプリにも応用できるので、今後Firestoreを使用する機会があれば活用していこうと思います。

---

# エンジニア募集中

Gakken LEAP では教育をアップデートしていきたいエンジニアを絶賛大募集しています！！

https://gakken-leap.co.jp/recruit/

---

# 参考

[Cloud Firestore](https://firebase.google.com/docs/firestore?hl=ja)