クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【Firebase】Realtime Databaseにブラウザから直接CRUD操作する

Firebaseの続きです。前回はアプリを追加してFirebase CLIでFirebase Hostingにデプロイしました。今回はRealtime Databaseを設定してブラウザから直接CRUDしてみます。Firebaseドキュメントが膨大で自分的には結構難産でした。内容はいつもDB事始めでやっているビートルズメンバーCRUDですw それではいきましょう!

【目次】

※参考:前回記事
【Firebase】アプリを追加してFirebase CLIでFirebase Hostingにデプロイする - クモのようにコツコツと

※参考:Web開発環境についてのまとめ
qiita.com

前回のおさらい

Firebase CLIでFirebase Hostingにデプロイ
https://cdn-ak.f.st-hatena.com/images/fotolife/i/idr_zz/20201230/20201230175351.jpg

詳細は前回記事を参照

※参考:【Firebase】アプリを追加してFirebase CLIでFirebase Hostingにデプロイする - クモのようにコツコツと

これまで触ったDB

自分のDB初体験はこんな感じ。

  • WordPressのMySQL:phpMyAdmin、AdminerなどのGUIツール
  • MAMPのMySQL:ターミナル、Express
  • JSON Server:ターミナル
  • MongoDB:ターミナル、Express
  • HerokuのPostgreSQL:ターミナル、Express

※参考:Web開発環境についてのまとめ(随時更新) - Qiita
※参考:Node.js / Expressを習得するためにやったことまとめ(随時更新) - Qiita

WordPress以外はまずターミナルからCRUD操作を試みた。その後サーバ側でExpressでAPIの設定をして、ブラウザ側からFetch APIでフォームデータを送ってCRUD操作した。

今回はFirebaseのRealtime Databaseを体験してみたい。HerokuのPostgreSQLに続きクラウドのDB。JSON形式のNoSQLのDBで、JSON Serverと似た感じがする。(Firebase にはMongoDBと似ているFirestoreもあるがこれはまた改めて…)

Realtime Databaseの設定

Realtime DatabaseもまずはターミナルからCRUD操作をすることから始めようと思ったのだが、ドキュメントで目立つのはこちらの「ウェブスタートガイド」。

※参考:Installation & Setup in JavaScript  |  Firebase Realtime Database

なんと、サーバ側のAPI設定も不要でブラウザ側から直接DBにCRUD操作できそう!とりあえずここから初めてみよう。

Firebase コンソールの [Realtime Database] セクションに移動

こんな画面。「データベースを作成」をクリック f:id:idr_zz:20210106063922j:plain

ロケーションを設定、日本はないようだったのでアメリカのままで次へ f:id:idr_zz:20210106064002j:plain

Firebase セキュリティ ルールの開始モードを選択

初期はロックモードになっている。読み書き禁止。 f:id:idr_zz:20210106064048j:plain

テストモードにすると1ヶ月後まで読み書きを許す内容。 f:id:idr_zz:20210106064115j:plain まずはテストモードで初めてみる。

ルールについて

※参考:Understand Firebase Realtime Database Rules
※参考:基本的なセキュリティ ルール  |  Firebase

DBが作成された! f:id:idr_zz:20210106064358j:plain 中身はまだない。

DBの設定と起動

DBの初期設定

次はDBの初期設定。

Realtime Database JavaScript SDK を初期化する

※参考:Installation & Setup in JavaScript  |  Firebase Realtime Database

このような設定をJSコードの頭の方に貼るようだ。

  // Set the configuration for your app
  // TODO: Replace with your project's config object
  var config = {
    apiKey: "<apiKey>",
    authDomain: "<projectId>.firebaseapp.com",
    databaseURL: "https://<databaseName>.firebaseio.com",
    storageBucket: "<bucket>.appspot.com"
  };
  firebase.initializeApp(config);
  • 変数configの値は連想配列
  • 連想配列の中でapiKeyauthDomaindatabaseURLstorageBucketを設定(< >は固有の値)
  • firebase.initializeApp()の引数にconfigを入れて初期設定

自分の場合はこうなる。

        // Config
        const config = {
            apiKey: "AIzaSyBju9iq3ug6gJMqyVsoGX_YByHt6L3Dh0c",
            authDomain: "kumokotsu-test.firebaseapp.com",
            databaseURL: "https://kumokotsu-test-default-rtdb.firebaseio.com",
            storageBucket: "kumokotsu-test.appspot.com"
        };
        firebase.initializeApp(config);

APIキーはJSコードやGitHubで公開しても大丈夫なのか不安になる。

しかし調べると「大丈夫だよ」という情報ばかりだった。大船に乗ったつもりでこのままいく!

※参考:FirebaseのAPIキーは公開しても大丈夫だよ(2020年夏) - shiodaifuku.io

DBを取得

そのあと、クラウドからDBを取得する

 const db = firebase.database();
  • 変数dbfirebase.database()を実行

これでRealtime Databaseが実行される。

HTMLを作成

前回のindex.htmlをコピーし「db-test.html」というファイルを作成。

このように書き換える

    <div id="message">
      <h2>りあるたいむ・でぇたべぇす</h2>
      <h1>CRUD操作テスト</h1>
      <p>ビートルズのメンバーをCRUDするの巻</p>
      <button type="button" class="crudBtn">実行</button>
    </div>

buttonタグ(.crudBtn)を押したときにCRUDを実行するようにしたい

ローカル起動

毎回クラウド上にデプロイするのは大変なのでローカル起動する。こちらのコマンド

$ firebase serve

※参考:Firebase CLI リファレンス

ブラウザを下記のURLを開く

http://localhost:5000/db-test.html

おお、ページが表示された! f:id:idr_zz:20210106065319j:plain

DB初期設定エラー

Dev-toolsのconsoleを見ると下記のエラーが。。

Uncaught FirebaseError: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).

これはページ読み込み時に実行される先ほどのDB初期設定がエラーになっているということだ。

調べるとfirebase.initializeApp()が複数回呼ばれていることが原因のようだった。

※参考:Firebaseで「Firebase App named '[DEFAULT]' already exists」というエラーがでた - Qiita

このようにしたらエラーが消えた。

        if (firebase.apps.length === 0) {
            firebase.initializeApp(config);
        };

if文でfirebase.appsがなければfirebase.initializeApp()を実行するようにしている。

しかしデベロッパーツールでif文にブレイクポイントを付けても止まらない。 f:id:idr_zz:20210106065716j:plain つまり、そもそもこのif文が動いてない。

試しに初期設定をコメントアウトしてみたが、問題なかった!

        /* const config = {
            apiKey: "AIzaSyBju9iq3ug6gJMqyVsoGX_YByHt6L3Dh0c",
            authDomain: "kumokotsu-test.firebaseapp.com",
            databaseURL: "https://kumokotsu-test-default-rtdb.firebaseio.com",
            storageBucket: "kumokotsu-test.appspot.com"
        };
        if (firebase.apps.length === 0) {
            firebase.initializeApp(config);
        }; */

おそらく前回インストールしたFirebaseのファイル一式の中にこちらの設定に相当する内容がすでに含まれているため、今回は設定が不要になったと思われる。(今後、必要な場面も出てくるかと思うので、このコメントはメモとして残しておく)

CRUD操作の設定

ドキュメント、参考記事

いよいよCRUD操作をしていく。基本的にはこちらのドキュメントを見ながら進める。

※参考:Read and Write Data on the Web  |  Firebase Realtime Database

メソッドの詳細はリファレンスを調べる。

※参考:Reference | JavaScript SDK  |  Firebase

その他参考にした記事。ドキュメントはいろんなページをぐるぐると循環してしまうがこちらの記事はとてもよりわかりすい!

※参考:FirebaseでCRUD処理の作成方法を解説 | とものブログ

DBの構造(ネストは浅く)

DBの構造についてはこちら。

※参考:Structure Your Database  |  Firebase Realtime Database

ネストは浅くして平坦にした方がパフォーマンスが良くなるようだ。

つっても今回はいつもDB事始めでやっているビートルズメンバーのCRUDなので、複雑になりようがないw

このような構造を想定している。

"member" {
    "1" {
        "id" : "1",
        "name" : "ジョン・レノン",
        "part": "ギター"
    }
}

データの追加(Create)

まずはCRUDのC、データの追加(Create)

※参考:Read and Write Data on the Web  |  Firebase Realtime Database

        // Create
        const createDB = (thisId, thisName, thisPart) => {
            db.ref('member/' + thisId).set({
                id: thisId,
                name: thisName,
                part : thisPart
            });
        };
  • 変数createDBの値は無名関数(引数は左からthisId, thisName, thisPart
  • dbref()メソッドを実行(引数はDBのパスでmember/thisIdを足した文字列)
  • 続いてset()メソッドを実行(引数はデータで連想配列)
  • 連想配列idキーの値はthisIdnameキーの値はthisNamepartキーの値はthisPart

引数の値を拾ってDBにデータを追加する。処理はメソッドチェーンになっている。


ref()メソッド

Returns a Reference to the Query's location.

クエリの場所への参照を返します。

※参考:Reference | JavaScript SDK  |  Firebase

クエリとはDBに対する命令文のこと

※参考:https://wa3.i-3-i.info/word11290.html


set()メソッド

Writes data to this Database location.

このデータベースの場所にデータを書き込みます。

※参考:Reference | JavaScript SDK  |  Firebase

データの取得(Read)

次はCRUDのR、データの取得(Read)

※参考:Read and Write Data on the Web  |  Firebase Realtime Database

        // Read
        const readDB = (thisId) => {
            db.ref('member/' + thisId).on('value', (snapshot) =>  {
                const data = snapshot.val();
                console.log(data.id);
                console.log(data.name);
                console.log(data.part);
            });
        };
  • 変数readDBの値は無名関数(引数はthisId
  • dbref()メソッドを実行(引数はDBのパスでmember/thisIdを足した文字列)
  • 続いてon()メソッドを実行(引数はvalueと無名関数、無名関数の引数はsnapshot
  • 無名関数の処理:変数datasnapshot.val()を実行
    コンソールにdata.iddata.namedata.part

引数のIDのデータをコンソールに表示する。


on()メソッド

Listens for data changes at a particular location.

特定の場所でのデータ変更をリッスンします。

リッスンしますてw

※参考:Reference | JavaScript SDK  |  Firebase


snapshot.val()メソッド

Extracts a JavaScript value from a DataSnapshot.

DataSnapshotからJavaScript値を抽出します。

※参考:DataSnapshot | JavaScript SDK  |  Firebase

snapshotとは「その瞬間」の状態を丸ごと保存したもののこと。

※参考:https://wa3.i-3-i.info/word14388.html

この処理で得たデータをコンソールに表示している。

データの更新(Update)

次はCRUDのU、データの更新(Update)

※参考:Read and Write Data on the Web  |  Firebase Realtime Database

全体の更新は先ほどのデータ追加で同じIDを指定すると上書きになるようだったが、名前だけ、パートだけなど部分的に更新したい。

まず名前の更新

        // Update (Name)
        const updateName = (thisId, thisName) => {
            db.ref('member/' + thisId).update({
                name: thisName,
            });
        };
  • 変数updateNameの値は無名関数(引数はthisIdthisName
  • dbref()メソッドを実行(引数はDBのパスでmember/thisIdを足した文字列)
  • 続いてupdate()メソッドを実行(引数は連想配列)
  • 連想配列のnameキーの値はthisName

引数でIDと名前を入れるとそのIDのデータの名前を更新できる。


次にパートの更新

        // Update (Part)
        const updatePart = (thisId, thisPart) => {
            db.ref('member/' + thisId).update({
                part: thisPart,
            });
        };
  • 変数updatePartの値は無名関数(引数はthisIdthisPart
  • dbref()メソッドを実行(引数はDBのパスでmember/thisIdを足した文字列)
  • 続いてupdate()メソッドを実行(引数は連想配列)
  • 連想配列のpartキーの値はthisPart

先ほどの名前更新と同じ構成でキーをpartにした形。


update()メソッド

Writes multiple values to the Database at once.

一度に複数の値をデータベースに書き込みます。

※参考:Reference | JavaScript SDK  |  Firebase

データの削除(Delete)

最後、CRUDのD、データの削除(Delete)

※参考:Read and Write Data on the Web  |  Firebase Realtime Database

        // Delete
        const deleteDB = (thisId) => {
            db.ref('member/' + thisId).remove();
        };
  • 変数deleteDBの値は無名関数(引数はthisId
  • dbref()メソッドを実行(引数はDBのパスでmember/thisIdを足した文字列)
  • 続いてremove()メソッドを実行

削除したいIDを指定してremove()を実行するだけ。シンプル。


remove()メソッド

Removes the data at this Database location.

このデータベースの場所にあるデータを削除します。

※参考:Reference | JavaScript SDK  |  Firebase

CRUD操作を実行

CRUD実行イベント

画面上にある「実行」ボタンを押すとCRUD操作を実行したい。 f:id:idr_zz:20210106065319j:plain

        // CRUD Event
        const crudBtn = document.querySelector('.crudBtn');
        crudBtn.addEventListener('click', () => {
            // 処理    
        }, false);
  • 変数crudBtnでDOM. crudBtnを取得
  • crudBtnをクリックしたら処理を実行

クリックイベントの中に先ほど作った関数を書く。

ブラウザ動作確認

まずcreateDB()でビートルズのリーダー、ジョンを追加する

        // CRUD Event
        const crudBtn = document.querySelector('.crudBtn');
        crudBtn.addEventListener('click', () => {
            createDB(1, 'ジョン・レノン', 'ギター');         
        }, false);

実行ボタンを押すとDBの1にジョン・レノンが追加された! f:id:idr_zz:20210107071205j:plain


以下、上記のイベント部分は省略し、中の処理のみ記載する(実際はイベントの中に書いている)

readDB()でデータを取得する。

readDB(1);

コンソールにジョンのデータが表示された! f:id:idr_zz:20210107071434j:plain


どんどんいこう、次はポールを追加!

createDB(2, 'ポール・マッカートニー', 'ギター');

2にポールが追加された! f:id:idr_zz:20210107071600j:plain


次はジョージを追加する。

createDB(3, 'ジョージ・ハリスン', 'ギター'); 

3でジョージが追加された! f:id:idr_zz:20210107071736j:plain クオリーメン生え抜きメンバーが揃った。


ベースのスチュを追加

createDB(4, 'スチュアート・サトクリフ', 'ベース');

4にスチュが追加された! f:id:idr_zz:20210107071834j:plain


ドラムのピートを追加

createDB(5, 'ピート・ベスト', 'ドラム');

5にピートが追加。デビュー前のハンブルグ巡業メンバーが揃った! f:id:idr_zz:20210107072019j:plain


しかしハンブルグでスチュ脱退、芸術家の道へ。deleteDB()で削除する。

deleteDB(4);

4のピートが削除された。。 f:id:idr_zz:20210107072234j:plain


ジョンの指名でポールがベースに転向〜updatePart()で更新。

updatePart(2, 'ベース');

2のポールのpartがベースになった! f:id:idr_zz:20210107072353j:plain


苦楽を共にしたピートがデビュー直前に解雇される。そりゃないよセニュリータ。。

deleteDB(5);

5のピートが削除された! f:id:idr_zz:20210107072528j:plain


最後にドラムのリンゴを追加。これでファブ・フォーが揃う!

createDB(6, 'リンゴ・スター', 'ドラム');

6にリンゴが追加された! f:id:idr_zz:20210108061222j:plain


最後に全員分のデータを読み込んでみる。

            readDB(1);
            readDB(2);
            readDB(3);
            readDB(6);   

コンソールに4人分表示される。 f:id:idr_zz:20210107072833j:plain

最終形

最終的にこうなった(ボタンを押すと一度に全部を実行する)

        // CRUD Event
        const crudBtn = document.querySelector('.crudBtn');
        crudBtn.addEventListener('click', () => {
            createDB(1, 'ジョン・レノン', 'ギター');
            // readDB(1);
            createDB(2, 'ポール・マッカートニー', 'ギター');
            createDB(3, 'ジョージ・ハリスン', 'ギター');          
            createDB(4, 'スチュアート・サトクリフ', 'ベース');
            createDB(5, 'ピート・ベスト', 'ドラム');
            deleteDB(4);
            updatePart(2, 'ベース');
            deleteDB(5);
            createDB(6, 'リンゴ・スター', 'ドラム');
            readDB(1);
            readDB(2);
            readDB(3);
            readDB(6);           
        }, false);

DB全体はこうなる。 f:id:idr_zz:20210107072918j:plain


ソース(GitHub)

※参考:firebase-hosting-test/db-test.html at bd53b79ce0b7c7f84d05f4a89091885deb97f0b9 · ryo-i/firebase-hosting-test · GitHub

Firebaseにデプロイ

デプロイ実行

これまでローカルで検証しいたが、完成したデータをFirebaseにデプロイする。*1

デプロイは下記のコマンド

$ firebase deploy

成功!

Deploy complete!

Firebase上でもページ(db-test.html)が表示された! f:id:idr_zz:20210106065319j:plain

※参考:Welcome to Firebase Hosting

ブラウザ動作確認

ボタンをクリックするとコンソールにデータが表示された! f:id:idr_zz:20210107072833j:plain

Dev-toolsの「Network」パネルを見ると f:id:idr_zz:20210108055119j:plain

いつもは「Response」だが今回は「Message」になっている。 f:id:idr_zz:20210108055230j:plain

中にはジョン・レノンのデータなどもある。 f:id:idr_zz:20210108055303j:plain

おまけ:DBルール変更

DBのルールを「ロック」に変更してみる。 f:id:idr_zz:20210107073200j:plain

コンソールを見ると読み書きは実行されているが、パーミッション関係のエラーも起きている。 f:id:idr_zz:20210107073234j:plain

※参考:FirebaseのDBのパーミッションのエラー - 万年素人からHackerへの道

読み書きが実行されて、エラーが起こるだけだとあまり意味がないな…。元のテストモードに戻した。

最後に

ということでRealtime Databaseにブラウザから直接CRUD操作できました*2

これまでブラウザ側にFetch API、サーバ側(Express)にCRUD操作、という構造にしていたのですが、Firebaseはブラウザ側にCRUD操作を書いて直接実行できたので楽に感じました。

また、RDBのようなテーブル設計をせずにいきなりデータを登録できたのも楽だなと感じました。逆にいうと無秩序なデータにもなりそうなんですが、ブラウザ側でフォーム項目や関数定義をちゃんと設定すればある程度は成立するんじゃないか、という気もします。


今後やってみたいこと:
FunctionsでAPIを作ってみたい。

※参考:Firebase Cloud Functions, Realtime DatabaseでCRUD REST WebAPIを作る - Qiita
※参考:Firebaseで動かすNode.jsアプリ入門🔥 - Qiita

Reactと連携してみたい。

(Reactime Database)
※参考:Firebase Realtime DatabaseをReactで使ってみる - Qiita

(Firestore)
※参考:新:React + FirestoreでCRUD(基礎編) - Qiita

それではまた!


※参考:Web開発環境についてのまとめ
qiita.com

*1:Firebaseが起動中の場合は「Control」+「C」で閉じる

*2:ここにいたるまでFirebaseのドキュメントに苦戦しました。まずドキュメントの中にウェブ、iOS、Android、サーバサイドなどの方法が全部入っており、知りたい情報にたどり着くのが一苦労。さらにいろんな場所がリンクし合ってグルグル巡回(Wikipediaサーフィンしている気分)。リファレンスページには機能の詳細が書かれているがここはここでまた膨大で。あと和訳されているページとされていないページが混在していて…