JavaScriptでFirebase(V9)を使う備忘録

JavaScriptでFirebase(V9)を使う備忘録

*Firebase(V9)ではモジュールバンドラを利用して、必要なものだけインポートします

ちなみに、Sparkプラン(Firebaseの無料プラン)で、無料の割り当て制限を超えた時はその月の残りの期間停止されます

目次
  1. 手順
  2. Cloud Firestore
  3. Firebase Authentication
  4. Cloud Storage

手順

  1. Firebaseプロジェクト作成
  2. アプリの登録
  3. アプリでFirebaseを初期化
  4. Firebaseアプリオブジェクトを作成

Firebaseプロジェクト作成
*プロジェクトの一意のID が生成されます
同じ「Firebaseプロジェクト」に登録されている「Firebaseアプリ」は、「Firebase Hosting」「Authentication」「Realtime Database」「Cloud Firestore」「Cloud Storage」「Cloud Functions」 など同じバックエンドを共有します

  1. Firebaseコンソールから「プロジェクトを追加」 をクリック、名前をつけます
  2. Google アナリティクスを設定(*省略可)

注意:実際に作成されるのはGCP(Google Cloud Platform)のプロジェクトです
FirebaseのプロジェクトとGCPのプロジェクトは連動しています
Sparkプラン(Firebaseの無料プラン)を選んでいるときは、GCP側のプロジェクトも非課金プロジェクトとして作成されます

アプリの登録

名前をつけて「コンソールに進む」をクリック

SDKインストール
*モジュールバンドラや JavaScriptフレームワークツールを利用(省略)

npm install firebase

アプリでFirebaseを初期化して、Firebaseアプリオブジェクトを作成します
*初期化すると、Firebaseサービス(Cloud Firestore・Authentication・Cloud Storageなど)を追加して使用できます

// 必要な機能をインポート
import { initializeApp } from "firebase/app";

// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: "・・・",
  authDomain: "・・・",
  projectId: "・・・",
  storageBucket: "・・・",
  messagingSenderId: "・・・",
  appId: "・・・"
};

// Firebaseを初期化
initializeApp(firebaseConfig);

ちなみに、アプリの設定からコピーできます
またアプリの削除やプロジェクトの削除ができます
注意:プロジェクトが完全に削除されるまで30日かかり、Sparkプラン(無料プラン)はプロジェクト数は通常は約 5~10に制限されているので、テストで何度も削除するときは要注意

FirebaseのAPIキーなどは公開情報です
クライアントがデータに直接アクセスできるため、本番環境では「セキュリティルール」を使用し、「Cloud FirestoreやCloud Storage」のデータを保護します
*セキュリティルールはドキュメント単位なので特定のフィールドだけに適応はできません

match /databases/{database}/documents:Firestore内のすべてのルート
{document=**} :のワイルドカード表記(データベース全体の任意のドキュメント)なので全てのデータ
allow read, write: if true :すべての読み書きを許可

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if
          request.time < timestamp.date(2022, 8, 14);
    }
  }
}

サインインしているユーザーに全てのデータの読み書きを許可

  match /{document=**} {
            allow read, write: if request.auth != null;
        }

例えば、データベースのルートにusersというコレクションを定義し、その中にユーザーIDに一致するドキュメントに対して、そのユーザーだけにデータの読み書きを許可する場合
*request.authは認証情報
*resource.dataはドキュメントに格納されているすべてのフィールドと値のマップ

match /users/{userId} {
   allow read, write: if request.auth != null && userId == request.auth.uid 
  }

match /hoge/{id}{
     allow read, write: if request.auth != null && resource.data.userId == request.auth.uid
 }

APIキーの制限について
本番環境では、APIキーに制限(提供したウェブサイトのリストからのみリクエストを受け入れる)をつけることができます

認証情報パネルを開き、プロジェクトを検索して、認証情報から「APIキー」選択、「制限タイプ」を「HTTP リファラー」にして「項目追加」でアプリのURLを登録、[保存] をクリックします
*「hoge.web.app/*」はhoge.web.app/ではじまるすべてのサイト

Cloud Firestore

無料枠は、プロジェクトあたり1GB のストレージ

データベースには、「Cloud Firestore」と「Realtime Database」があります
「Cloud Firestore」 は「Realtime Database」より高速なクエリと高性能なスケーリングです

データベースを作成する

  • セキュリティルールの開始モードを選択します
    開発中は、「テストモード」を選択し、本番環境にデプロイ前にセキュリティルールを更新します
  • リソースロケーションの選択(まだ選択していない場合)
    「asia-northeast1 東京・asia-northeast2 大阪」があります
    念の為:Blazeプラン(課金プラン)の場合、Cloud Storage(画像のアップロード)で無料プログラム「Always Free」(5GB /月の Regional Storage)を利用できるのは「 us-east1・us-west1・us-central1」 リージョンのみです

「Cloud Firestore・ Cloud Storage」のどちらかを使い始めると、プロジェクトのデフォルトのGoogle Cloud Platform(GCP)リソースロケーションを選択が求められます
プロジェクトのデフォルトの GCPリソース ロケーションをいったん設定すると、変更できません
「Cloud Firestore・Cloud Storage・Google App Engine」の同じプロジェクトは、デフォルトのGCPリソースロケーションを共有し、次のいずれかのサービスでロケーションを設定すると、他のサービスのロケーションも設定されます

データはドキュメントのコレクションとして保存します
*コレクションはドキュメントのコンテナです

Cloud Firestore データモデル(ドキュメントより)

Cloud Firestore データモデルについて(*特記事項のみ)

  • データ型
    map:ドキュメント内に埋め込まれたオブジェクト
    reference:パスそのものを保持する 例: projects/[PROJECT_ID]
  • ドキュメントには「サブコレクション」や「ネストされたオブジェクト」を格納できます
    サブコレクションとは、ドキュメント内に追加するコレクションです(データを階層的に構造化)
    同じコレクションIDを持つ複数のサブコレクションに対してクエリを実行できます
    *ドキュメントを削除しても、そのドキュメント内のサブコレクションは削除されません

ドキュメントまたはコレクションへのパスを文字列として指定、スラッシュ「/」で区切ってリファレンスを作成できます

アプリに「Firestore」を追加します
* Firestore Liteについて: リアルタイムストリーミングを使わないときは「Firestore Lite」にすると軽量化できます

import { getFirestore } from "firebase/firestore";
//Firestore Liteの場合
import { getFirestore } from '@firebase/firestore/lite';

コレクションへの参照を取得 collection()

addDoc()を利用して、新しいコレクション(例:users)とドキュメントを作成
識別子は自動的で生成
*コレクション内のドキュメントには、異なる情報のセット(keyとvalue)を含めることもできます

import { getFirestore, collection, addDoc } from "firebase/firestore";
//Cloud Firestoreを初期化し、サービスへの参照を取得
const db = getFirestore();
try {
  const docRef = await addDoc(collection(db, "users"), {
    name: "花子",
    age: 20
  });
  console.log(docRef.id);
} catch (e) {
  console.error(e);
}

コレクションやドキュメントが存在しない場合は作成され、コレクション内のすべてのドキュメントを削除すると、コレクションも削除されます

注意:以降 const db = getFirestore();は省略しています

getDocs()でデータを取得します

import { collection, getDocs } from "firebase/firestore";

getDocs(collection(db, "users"))
  .then(snapshot => {
    let users = []
    snapshot.docs.forEach(doc => {
      users.push({ ...doc.data(), id: doc.id })
    })
    console.log(users)
  })
  .catch(err => {
    console.log(err.message)
})

クエリオブジェクトを作成、getDocs()で結果を取得します

import { collection, getDocs, query, where } from "firebase/firestore";

//usersコレクションからageが20のusersを返します
const q = query(collection(db, "users"), where("age", "==", 20));
//結果を取得
await getDocs(q)
  .then(snapshot => {
    let users = []
    snapshot.docs.forEach(doc => {
      users.push({ ...doc.data(), id: doc.id })
    })
    console.log(users)
  })
  .catch(err => {
    console.log(err.message)
})

リアルタイムデータを取得する場合(Firestore Liteは利用できません)
onSnapshot() を使用してドキュメントをリッスンします(データの書き込み→データをリスナーに通知→データをバックエンドに送信)
コールバックを使用して、すぐにドキュメントスナップショットが作成されます
*リッスンする必要がなくなったら、コールバックが呼び出されないようにリスナーを停止

import { collection, onSnapshot } from "firebase/firestore";

const unsubscribe = onSnapshot(collection(db, "users"),  (snapshot) => {
  let users = []
  snapshot.docs.forEach(doc => {
    users.push({ ...doc.data(), id: doc.id })
  })
  console.log(users)
});

// Stop
unsubscribe();

単一ドキュメントへの参照を取得 doc()

setDoc()doc()を利用して、単一のドキュメントを作成または上書きします
この場合は、ドキュメントIDを指定する必要があります
ドキュメントが存在しない場合は、ドキュメントは新規に作成されます
ドキュメントが存在する場合、コンテンツが上書きされます()
*マップを含むドキュメントの場合は空のマップを含むフィールドをセットするとドキュメントは上書きされます

import { doc, setDoc, Timestamp } from "firebase/firestore";

const data = {
    name: "太郎",
    age:30,
    date: Timestamp.fromDate(new Date()),
};
//usersコレクションに、ドキュメントIDはnew-users-id
await setDoc(doc(db, "users", "new-users-id"), data);

getDoc()で、単一のドキュメントを取得する

import { doc, getDoc } from "firebase/firestore";

getDoc(doc(db, 'users', 'new-users-id'))
.then((doc)=>{
 console.log(doc.data(), doc.id)
})

updateDoc()で、シングルドキュメントの一部のフィールドを更新します

import { doc, updateDoc } from "firebase/firestore";

await updateDoc(doc(db, "users", "new-users-id"), {
  "age": 25
});

deleteDoc()は、ドキュメントを削除するために使用します

import { doc, deleteDoc } from "firebase/firestore";

//dataコレクションのoneドキュメントを削除
await deleteDoc(doc(db, "users", "new-users-id"));

Firebase Authentication

*FirebaseUIではありません

Firebase Authenticationを利用するとユーザー認証機能を独自で実装する必要がありません
さまざまの認証方法が提供されています
ぼほ無料で利用できます(電話番号認証は従量課金)

import { getAuth } from "firebase/auth";

const auth = getAuth();

createUserWithEmailAndPassword() を使用して、メールアドレスとパスワードでユーザーを登録する

import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";
const auth = getAuth()

const email = 'hogehoge@gmail.com'
const password = 'test1234'

createUserWithEmailAndPassword(auth, email, password)
  .then((userCredential) => {
    console.log(userCredential.user)
  })
  .catch((err) => {
   console.log(err.message)
  });

updateProfile()を使用して、ユーザーの基本的なプロフィール情報(ユーザーの表示名とプロフィール写真の URL)を更新できます

import { getAuth, createUserWithEmailAndPassword, updateProfile} from "firebase/auth";
const auth = getAuth()

const email = 'hogehoge@gmail.com'
const password = 'test1234'
const displayName = '花子'

createUserWithEmailAndPassword(auth, email, password)
 .then((userCredential) => {
  console.log(userCredential.user), 
  updateProfile(userCredential.user, {displayName: displayName})
  })
 .catch((err) => {console.log(err.message) })

signOut()を使用して、ログアウトします

import { getAuth, signOut } from "firebase/auth";
const auth = getAuth() 

signOut(auth)
   .then(() => {
     console.log('ログアウトしました')
   })
   .catch(err => {
    console.log(err.message)
 })

signInWithEmailAndPassword()を使用して、既存のユーザーでログインします

import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
const auth = getAuth();

const email = 'hogehoge@gmail.com'
const password = 'test1234'

signInWithEmailAndPassword(auth, email, password)
  .then((userCredential) => {
   console.log(userCredential.user)
  })
  .catch((err) => {
    console.log(err.message)
  });

onAuthStateChanged() : 認証状態オブザーバーを設定してアプリで認証状態をチェックします
*オブザーバーは、ユーザーのログイン状態が変わるたびに呼び出されます

import { getAuth, onAuthStateChanged } from "firebase/auth";

const auth = getAuth();
onAuthStateChanged(auth, (user) => {
  if (user) {
    console.log(user.uid)
  } else {
    console.log('ログアウトしました')
  }
});

Cloud Storage

*Blazeプラン(課金プラン)で無料プログラム「Always Free」(5GB /月の Regional Storage)を利用できるのは「 us-east1・us-west1・us-central1」 リージョンのみです
Regional Storage:特定の地域から頻繁にアクセスされるデータの保存
データはGoogle Cloud Storageバケットに格納されます

画像や動画をアップロードし、アップロードした画像のURLをデータベース(Cloud Firestoreなど)に保存

ファイルの「アップロード・ダウンロード・削除・メタデータを取得や更新」はファイルへの参照を作成する必要があります

import { getStorage, ref } from "firebase/storage";
//ストレージサービスへの参照
const storage = getStorage();

//ファイル(images/hoge.jpg)への参照を作成
const hogeRef = ref(storage, 'images/space.jpg');

余談:Vue.jsのComposition APIで「ref」を使う時は、firebase/storageでは「ref as fref」など別名でインポート

  • parentプロパティ(hogeRef.parent)で、1つ上に移動・
  • rootプロパティで最上位(hogeRef.root)に移動できます
  • fullPathプロパティはファイルのフルパス
  • nameプロパティはファイル名を取得します

「Cloud Storage」のファイル操作は、デフォルトでは「Firebase Authentication」が必要です
認証されていないアクセスを許可するにはCloud Storageのセキュリティルールを変更します
if request.auth != null;の部分をif true;

HTMLのinputタグのtypeをfileからアップロードするファイル名を取得
uploadBytes()で、画像をアップロードして、getDownloadURL()で、ファイルのパスを取得します

<input type="file" id="file">
import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage";
//inputタグに、onchangeでファイルが選択された際の処理を指定しています
let fileInput = document.getElementById('file');
fileInput.onchange = (e) => {
 const file = e.target.files[0]

 //ランダム文字列をプラス(おまけ)
 const S ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
 const N = 16;
 const randomChar = Array.from(crypto.getRandomValues(new Uint32Array(N)))
 .map((n) => S[n % S.length]).join("");
 const fileName = randomChar + "_" + file.name;

 const storage = getStorage();
 //ファイルへの参照
 const storageRef = ref(storage, fileName);

 uploadBytes(storageRef, file)
 .then(() => {
  return getDownloadURL(storageRef)
 })
 .then((res) => {
  console.log(res)
 })
 .catch((err) => {
  console.log(err.message)
  });
};

deleteObject()で、アップロードしたファイルを削除します

import { getStorage, ref, deleteObject } from "firebase/storage";

const storage = getStorage();
const desertRef = ref(storage, '削除するファイルのパス');

deleteObject(desertRef)
.then(() => {
  console.log('削除されました')
}).catch((err) => {
  console.log(err)
});