React + Firestore【入門から実装まで】



↓応援クリックよろしくお願いします↓

にほんブログ村 IT技術ブログ プログラム・プログラマーへ




本稿ではfirestoreの概要、NoSQLデータベースの構造、firestoreの初期設定、Reactの初期設定、書き込み、react-firebase-hooks(useCollection、useDocument)を使った読み込み方法(実装方法)まで丁寧に解説しています。

Firestoreとは

Firestoreとはfirebase(Google Cloud Platformとほぼ同義)内で利用できるのNoSQLのデータベースで、最大の特徴はReact等フロントエンドから直接DBを操作できる点です。バックエンドを記述する必要がなく、フロントエンド側でfirebaseライブラリーを使ってDBへの書き込み・読み込みが簡単にできます(もちろんバックエンド側からも接続できます)。下記のイメージ図はいわゆるMERNスタック(上段)と、今回ご紹介するReact + firestore構成(下段)の比較です。後者はシンプルな構造です。

なおフロントエンド(クライアント)からのリクエストの処理(アクセス権限)は、MERNスタックではバックエンドで管理しますが、firestoreでは独自のセキュリティールールで設定します。今回はセキュリティールールを定めないテスト環境でご紹介します。




Firebase内のfirestoreをデータベースとするなら、ホスティングもfirebaseを使用するのが無難です(他も使用可能)。firebaseは以前のこちらの記事でもご紹介したように、AWSのような複雑な設定なくして、簡単にWebサービスを立ち上げることができるインフラです。

大規模なWebサービスでは、これまでフロントエンドエンジニア、バックエンドエンジニア、それに主にAWSを念頭にインフラエンジニアの3職種(3技能)の連携が不可欠でした。一人で性格の異なる3技能をこなすのは容易ではなかったのですが、firebase(インフラ)とfirestore(データベース)を使用することで、フロントエンドエンジニアが簡単にWebサービスを立ち上げられるようになりました。他方で両者はAWSと双璧を成すGCP(Google Cloud Platform)の上で動いていることから、簡単であるといっても大規模なWebサービス(重いアクセス)にも対応できます。

またfirestoreでは、firebaseに以前からあるリアルタイムデータベースのように全てのデータを読み込んでクライアントと同期させるのではなく、都度必要な部分だけを読み込む方法を採用することで、処理がより高速化されています。

Firestoreのコレクション – ドキュメント構造

続いてfirestoreで採用されているNoSQL構造(うち、ドキュメント指向)について、ご紹介します。NoSQLデータベースは、下記Wilipediaの定義にある通り、リレーショナルデータベース以外のDBという消極的概念で、異なる様々なDB構造の総称です。

NoSQL(一般に “Not only SQL” と解釈される)とは、関係データベース管理システム (RDBMS) 以外のデータベース管理システムを指すおおまかな分類語である。関係データベースを杓子定規に適用してきた長い歴史を打破し、それ以外の構造のデータベースの利用・発展を促進させようとする運動の標語としての意味合いを持つ。

Wikipedia

いずれのNoSQLデータベースにおいても共通する点は、RDBMS(エクセル)のようにテーブル(シート)、カラム(列)、レコード(行)を持たない点で、firestoreで採用されているドキュメント指向でも同様です。

Firestoreは、下記のようなCollection(コレクション) – Document(ドキュメント)構造をしています。まずは起点のRoot(ルート)からコレクションに繋がります。ルートから始まるコレクション(ルートコレクション)は下記の図では1つですが、複数あってもかまいません。

コレクションは、その内側に複数のドキュメントを保持します。言い換えると、ドキュメントの集合がコレクションです。一つ一つのドキュメントは自動的に付与される固有のキーと、Javascriptのオブジェクト(json)と同様のkey : valueのペアで構成されるデータを持ちます。Firestoreではドキュメントの内容はField(フィールド)と呼ばれます。なお、コレクションには固有のキーが自動的に付与されず、コレクションの名称が固有のキーの役割を果たします。

上記イメージ図の右上にある通り、ドキュメントはその内容としてフィールドに加えて、サブコレクションを持つこともできます。チャットアプリを例に挙げると、(1)チャットのルームの集まりがコレクション、(2)各チャットルームを識別する単位がドキュメント(フィールドとしてルーム名を保持)、(3)各チャットルーム内のチャットの集合は(2)から派生するサブコレクション、(4)各チャットメッセージは同サブスクリプション内のドキュメントで、ユーザー情報、チャット本文、添付ファイル、投稿日時などのフィールドを持つといった具合です。

下記はfirestoreで実際に上記チャットルームのデータベースを組み立てた例です。(1)に対応するのが最も左側にあるroomsです。中央には二つのキーがあり、それぞれが(2)ドキュメント(チャットルーム)に対応します。このドキュメントはフィールドとして、右側下段のチャットルーム名{name: “New Channel”}を保持する他、右側上段に(3)サブコレクションとして”messages”を持ちます。



サブコレクション”messages”はチャットの集合ですので、中を開くと下記のようにそれぞれのチャットがドキュメントとして格納されています。下記は上記とそっくりに見えますが、最も左側のコレクションがroomsではなく、サブコレクションの”messages”になっています。Firestoreの画面上では1階層のコレクション-ドキュメントしか表示されないので、階層をサブコレクションに掘り下げると、上位のコレクション階層は画面上からは消えます。

messagesサブコレクションですが、中央のドキュメントのキーを見ると、2件のドキュメント(チャット)が投稿されていることがわかります。また、最も右側を見ると、うち1件のチャットのドキュメントに係るフィールドとして、メッセージの内容、投稿の時間、ユーザー名、ユーザーイメージのURLの4件の情報が格納されていることがわかります。




後ほど詳しく解説いたしますが、Reactからfirestoreのデータにアクセス(書き込み又は読み込み)するときは、次のようなコレクションとドキュメントが連なったコードを書きます。



db.collection(…).doc(…).collection(…).doc(…)

Reactにおけるfirestoreの使い方


Firebase上の設定

Firestoreを使用する前に、firebaseでの初期設定(アプリ登録等)が必要になります。私のこちらの記事「FirebaseでWebアプリをデプロイする方法」の1章「Firebaseのサイト上での初期設定」と、8章「Firebaseプロジェクト内でのアプリの登録方法」でご紹介した手順に従って設定をします。また、同記事の2章の冒頭にある次のコードも実行します。2章の以降の内容と、7章まではアプリが完成した後のホスティングの方法のご紹介ですので、firestoreの設定をしている開発段階ではスキップします。

npm install firebase
npm install -g firebase-tools


firebase.js

次のコードは8章の最後でご紹介したfirebase.jsのからデータベースに関連するところを抜粋したものです(firebaseConfigの内容は省略)。

1行目でfirebaseライブラリーをインポートして、2行目をConfigを設定した後、同Configを引数とするfirebase.initializeAppオブジェクトを設定、FirebaseAppに同オブジェクトを格納しています。initializeAppメソッドがフロントエンドとfirebaseを接続する役目を果たしています。

下から2行目では、firebaseAppのfirestoreメソッドでfirestoreと接続し、接続をdbと定義、最終行で同dbをエクスポートしています。dbの中にはConfigの内容も含まれてフロントエンドとfirestoreの接続が維持されるので、他のシートでこのdbオブジェクトをインポートして使用するだけでfirestoreとの接続が可能になります。

firebase.js

import firebase from “firebase”;

const firebaseConfig = {
…};
const firebaseApp = firebase.initializeApp(firebaseConfig);
const db = firebaseApp.firestore();

export default db;

Firestoreの立ち上げ

firebase.jsの作成を終えたら、firebaseのサイト内でfirestoreデータベースの設定をします。firebaseのホームページに戻り、console画面に入ります。前述firebase上の設定で作成したプロジェクトを選択します。


プロジェクト内に入ったら、左サイドバーからFirestore Databaseを選択します。中央のCloud Firestoreを選択しても同じです。



続いて画面中央のCreate databaseをクリックします。



production modeかtest modeを選ぶポップアップが出ますので、いったんtest modeで始めます。本番運用するときに、別途詳細なセキュリティールールを定めてproduction modeに移行します。



前の画面で右下のNextを選ぶとサーバの場所を選べますので、US centralなどMulti-regionのどこかを選択します。最後に右下のenableを押すと下記のような画面になります。firestoreの立ち上げは30秒ほどで終わります。



テスト用のセキュリティールールの設定


セキュリティールールでは、データベースのアクセス権限を定めます。ドキュメント毎に、読み込み、書き込み、消去の細かな設定ができます。test modeの初期設定では、誰でもデータベースにアクセスできるようになっていますが、期間が1ヶ月間に制限されています。いったんこの1ヶ月制限を切ります。Cloud Firestoreの画面でRulesを選択し、下記画面の灰色の部分を消去します。最後のセミコロンは残します。



消去の後、Publishを選択します。前述したように、このテスト環境の設定では誰でもデータベースを操作できるので、本番運用時はセキュリティールールの設定を要します。



Firestoreデータベースへの書き込み方法



次に具体的にCollection(コレクション) – Document(ドキュメント)構造のfirestoreデータベースを作成します。ここでは、ドキュメント指向データベースの説明で例として用いたチャットのデータベースを作りたいと思います。

コレクションの追加は、firestoreの画面からでもGUI操作でできるのですが、先ほどdbオブジェクトを作りましたので、フロントエンド側からの操作方法をご紹介します。

Chat.jsというコンポーネント内に設置したボタンをクリックすると、addChatという関数を呼び出され、その関数によってfirestore内に起点のrootから繋がる最上位のコレクション”rooms”内にドキュメントを作成したいと思います。各ドキュメントの名称はpromptでユーザーが決められるようにします。下記は、Chat.jsの関数部分です。ボタンを設置するJSXの部分(returnの内側)の表記は割愛しています。



Chat.js

import React from “react”;
import db from “./firebase”;

function Chat() {
const addChat = () => {
const chatName = prompt(“Please enter the chat room name”);

if (chatName) {
db.collection(“rooms”).add({
name: chatName,
});
}
};

return (
);
}

export default Chat;

2行目で先ほどfirebase.jsで作成・エクスポートしたdb(フロントエンドからfirestoreへの接続オブジェクト)をインポートしています。

関数コンポーネントChat内の1行目でAddChat関数を定義し、関数内ではまず、promptでユーザーからのインプットをchatNameに格納しています。これは後にドキュメントの名称(チャットルームの名前)になります。

続いて、db.collection(“rooms”)の部分で最上位のroomsコレクションを設置し、.addメソッドでこのコレクションにドキュメントを加えています。.addメソッドにはnameをkeyに、ユーザーからのインプットをvalueとしたオブジェクトを引数として渡します。このオブジェクトは冒頭のfirestoreの構造で紹介したフィールドに相当し、オブジェクト構造の任意のデータを格納できます。以上の操作でroomsコレクションの中に、ユーザーがインプットした値(ここではチャットの部屋の名称)を保持するドキュメントが作成されます。

実際に、フロントエンドから上記の操作をして、firestoreを見ると次のようになります。promptでは”TestChatRoom”と入力しました。

冒頭の説明を繰り返すと、左側のroomsがコレクション、中央の暗号のようなものがコレクション内にあるドキュメントの固有のキー、右側にあるのがドキュメントの内容にあたるフィールドです。ドキュメントの中には.addで加えてオブジェクト{name : “TestChatRoom”}が格納されています。


サブコレクションを加える方法

ドキュメントの下に更にサブコレクションを加える場合、次のようなコードになります。idの部分にはドキュメントの固有キーを入れます。ドキュメントの固有キーは、Reduxなどを使いユーザーからのインプットに応じて格納したキー(doc.id)を使用するのが通常だと思いますが、firestoreの画面にある具体的なキー、例えば”ge3QDAXvi4yTZfATwRo6″を.doc()に直接引数として渡すこともできます。サブコレクションの名称はここでは”room”としました。.add以降は上記の1階層の場合と同様に、key:valueペアからなるドキュメントの内容(フィールド)を記述します。

db.collection(“rooms”).doc(id).collection(“room”).add{
}


Firestoreデータベースの読み込み方法

Firestoreデータベースからの読み込みには、以前こちらの記事でもご紹介したreact-firebase-hooskを使います。まずはインストールします。

npm install react-firebase-hooks

raect-firebase-hooksには、firestoreを読み込むhooksが全部で8つ(コレクションを読むhooksが4つ、ドキュメントを読むhooksが4つ)用意されていますが、始めにコレクションを読み込むuseCollectionを、コンソール画面に表示するだけの簡単な例でご紹介します。コンポーネントの名称は仮にReadDb.jsとし、JSXの部分は省略しています。

useCollection

関数コンポーネントの冒頭ではuseCollectionと、firebase.jsでエクスポートしたdbをインポートします。

関数の内側の1行目でuseCollectionを呼び出しています。useCollecitonはコレクションを読むhooks(関数)ですので、引数にはいずれかのコレクションを指定します。ここでは最上位に指定したroomsコレクションを指定しています。

戻り値は(1)コレクションの内容の他、(2)ロード中か否か(boolean)を示すloadと、(3)エラーが返ってきた場合の内容が格納されるerrorの3つがあります。名称は(2)がloading、(3)はerrorとするのが通例ですが(1)はなんでも構いません。公式ドキュメントではsnapshotやvalueという名称を使用していますが、ここではわかりやすく(1)をcollectionsとして受けています。


ReadDb.js

import React from “react”;
import { useCollection } from “react-firebase-hooks/firestore”;
import db from “./firebase”;

function ReadDb() {
const [collections, loading, error] = useCollection(db.collection(“rooms”));
collections?.docs.map((doc) => {
console.log(doc.data().name);
console.log(doc.id);
});

return (
);
}

export default ReadDb;

先ほどご紹介したように、コレクションは複数のドキュメントの集合ですので、具体的に内容を見るために、collections?.docsのコードでコレクションの内部のドキュメントの集合を呼び出しています。今回の例ではドキュメントは1つですが、通常、コレクション内には複数のドキュメントがあるのでmap関数を用いてループ処理します。

data()メソッドでドキュメントの中のフィールド(実際のデータ)にアクセスできます。data()メソッドを適用しない場合は、ドキュメントのメタデータも含んだオブジェクトが返されます。今回のフィールドの内容は{name : “TestChatRoom”}の1対ですので、.nameとすると”TestChatRoom”が呼び出され、上記コードではコンソール画面に表示されます。なお、前述サブコレクションはフィールドの一部ではないので、data()メソッドで得られる戻り値にも含まれておりません。

続く行のdoc.idはドキュメントに固有のidを呼び出しています。Firestoreの画面上とコードを結びつけると次のようなイメージになります。

useDocument

最後にuseDocumentをご紹介します。こちらは名前の通り、複数のドキュメントの集合のコレクションではなく、特定のドキュメントの内容を取得します。ここでは、roomsコレクションの中の特定のドキュメント内に、roomサブコレクションがあり、さらにその中のドキュメントがあるとします(前述「サブコレクションを加える方法」)。

useDocumentのインポートの方法は、useCollectionと同様です。関数の戻り値が3つある点も一緒です。引数の内容は異なり、useCollectionではコレクションを渡しましたが、useDocumentはドキュメントを渡します。.doc(id)のidの部分には前述の通り、ドキュメントに固有のidを渡します。

useDocumentは内容が特定の一つのドキュメントですので、useCollectionのようにmap関数を使わずに、ダイレクトに.data()メソッドを呼び出してフィールドを取得・表示できます。


ReadDb.js

import React from “react”;
import { useDocument } from “react-firebase-hooks/firestore”;
import db from “./firebase”;

function ReadDb() {
const [docDetails, loading, error] = useDocument(
db.collection(“rooms”)
.doc(id)
.collection(“room”)
.doc(id)
);
console.log(docDetails?.data());
};

return (
);
}

export default ReadDb;

今日も最後まで読んで頂きありがとうございました。