Cloud Firestore Web Codelab

1。概要

目標

このコードラボでは、あなたがで駆動レストラン勧告のWebアプリ構築しますクラウドFirestoreを

img5.png

あなたが学ぶこと

  • WebアプリからCloudFirestoreへのデータの読み取りと書き込み
  • CloudFirestoreデータの変更をリアルタイムで聞く
  • Firebase認証とセキュリティルールを使用してCloudFirestoreデータを保護します
  • 複雑なCloudFirestoreクエリを作成する

必要なもの

このコードラボを開始する前に、次のものがインストールされていることを確認してください。

2.Firebaseプロジェクトを作成して設定します

Firebaseプロジェクトを作成する

  1. Firebaseコンソール、クリックしFirebaseプロジェクトFriendlyEatsに名前付け、プロジェクトを追加します

FirebaseプロジェクトのプロジェクトIDを覚えておいてください。

  1. プロジェクトの作成]クリックします。

これから作成するアプリケーションは、ウェブ上で利用可能ないくつかのFirebaseサービスを使用します。

  • Firebase認証は、簡単にユーザーを識別するために
  • クラウド上の構造化データを保存し、データが更新されたときにインスタント通知を取得するためのクラウドFirestore
  • Firebaseあなたの静的な資産をホストするホスティングおよびサーブ

この特定のコードラボでは、FirebaseHostingを既に構成しています。ただし、FirebaseAuthとCloudFirestoreの場合は、Firebaseコンソールを使用したサービスの構成と有効化について説明します。

匿名認証を有効にする

このコードラボでは認証の焦点はありませんが、アプリに何らかの形式の認証を含めることが重要です。ユーザーは黙って要求されることなく、中に署名されることを意味します-私たちは、匿名ログインを使用します。

あなたは匿名ログインを有効にする必要があります

  1. Firebaseコンソールでは、左側のナビゲーションでビルドセクションを見つけます。
  2. 認証をクリックし、サインインし、メソッドタブをクリックします(または、こちらをクリックしてください直接そこに行きます)。
  3. 匿名サインインプロバイダを有効にし、[保存]クリックします。

img7.png

これにより、ユーザーがWebアプリにアクセスしたときに、アプリケーションがサイレントにサインインできるようになります。読み気軽匿名認証のマニュアルを参照して詳細をご覧ください。

CloudFirestoreを有効にする

このアプリはCloudFirestoreを使用して、レストランの情報と評価を保存および受信します。

CloudFirestoreを有効にする必要があります。 Firebaseコンソールの[ビルド]セクションで、Firestoreデータベース]クリックします。クラウドFirestoreペインでデータベースを作成]をクリックします。

Cloud Firestoreのデータへのアクセスは、セキュリティルールによって制御されます。ルールについてはこのコードラボの後半で詳しく説明しますが、開始するには、最初にデータにいくつかの基本的なルールを設定する必要があります。で規則]タブFirebaseコンソールの次のルールを追加して公開]をクリックします。

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      //
      // WARNING: These rules are insecure! We will replace them with
      // more secure rules later in the codelab
      //
      allow read, write: if request.auth != null;
    }
  }
}

上記のルールは、サインインしているユーザーへのデータアクセスを制限します。これにより、認証されていないユーザーが読み取りまたは書き込みを行うことができなくなります。これは、パブリックアクセスを許可するよりも優れていますが、まだ安全とは言えません。これらのルールは、コードラボの後半で改善されます。

3.サンプルコードを取得します

クローンGitHubのリポジトリ、コマンドラインから:

git clone https://github.com/firebase/friendlyeats-web

サンプルコードは📁中にクローン化されている必要がありますfriendlyeats-webディレクトリ。今後は、必ず次のディレクトリからすべてのコマンドを実行してください。

cd friendlyeats-web

スターターアプリをインポートする

(... WebStorm、アトム、サブライム、Visual Studioのコード)あなたのIDEを使用して開いたり📁インポートfriendlyeats-webディレクトリを。このディレクトリには、まだ機能していないレストラン推奨アプリで構成されるcodelabの開始コードが含まれています。このコードラボ全体で機能するようにするため、すぐにそのディレクトリのコードを編集する必要があります。

4.Firebaseコマンドラインインターフェースをインストールします

Firebaseコマンドラインインターフェース(CLI)を使用すると、ウェブアプリをローカルで提供し、ウェブアプリをFirebaseHostingにデプロイできます。

  1. 次のnpmコマンドを実行してCLIをインストールします。
npm -g install firebase-tools
  1. 次のコマンドを実行して、CLIが正しくインストールされていることを確認します。
firebase --version

FirebaseCLIのバージョンがv7.4.0以降であることを確認してください。

  1. 次のコマンドを実行して、FirebaseCLIを承認します。
firebase login

アプリのローカルディレクトリとファイルからFirebaseHostingのアプリの構成を取得するようにウェブアプリテンプレートを設定しました。ただし、これを行うには、アプリをFirebaseプロジェクトに関連付ける必要があります。

  1. コマンドラインがアプリのローカルディレクトリにアクセスしていることを確認してください。
  2. 次のコマンドを実行して、アプリをFirebaseプロジェクトに関連付けます。
firebase use --add
  1. プロンプトが表示されたら、あなたのプロジェクトIDを選択し、あなたのFirebaseプロジェクトの別名を与えます。

エイリアスは、複数の環境(本番環境、ステージングなど)がある場合に役立ちます。しかし、このコードラボのために、ちょうどの別名使用してみましょうdefault

  1. コマンドラインの残りの指示に従います。

5.ローカルサーバーを実行します

私たちは実際に私たちのアプリで作業を開始する準備ができています!アプリをローカルで実行しましょう!

  1. 次のFirebaseCLIコマンドを実行します。
firebase emulators:start --only hosting
  1. コマンドラインに次の応答が表示されます。
hosting: Local server: http://localhost:5000

私たちは、使用しているFirebaseホスティングローカルに我々のアプリを提供するためにエミュレータを。ウェブアプリは、今から入手する必要があります。http:// localhostを:5000

  1. であなたのアプリを開きます。http:// localhostを:5000

Firebaseプロジェクトに接続されているFriendlyEatsのコピーが表示されます。

アプリは自動的にFirebaseプロジェクトに接続し、匿名ユーザーとしてサイレントサインインします。

img2.png

6. CloudFirestoreにデータを書き込みます

このセクションでは、アプリのUIにデータを入力できるように、CloudFirestoreにデータを書き込みます。これは、経由して手動で行うことができますFirebaseコンソールが、私たちは、基本的なクラウドFirestore書き込みを実証するためのアプリ自体にそれをやります。

データ・モデル

Firestoreデータは、コレクション、ドキュメント、フィールド、およびサブコレクションに分割されます。我々はと呼ばれるトップレベルのコレクション内の文書としての各レストランを格納するrestaurants

img3.png

その後、我々はと呼ばれるサブコレクション内の各評価を保存するよratings各レストランの下で。

img4.png

Firestoreにレストランを追加する

アプリの主なモデルオブジェクトはレストランです。のは、レストランのドキュメントを追加し、いくつかのコードを記述してみましょうrestaurantsコレクションを。

  1. ダウンロードしたファイルは、開いてから、 scripts/FriendlyEats.Data.js
  2. 機能の検索FriendlyEats.prototype.addRestaurant
  3. 関数全体を次のコードに置き換えます。

FriendlyEats.Data.js

FriendlyEats.prototype.addRestaurant = function(data) {
  var collection = firebase.firestore().collection('restaurants');
  return collection.add(data);
};

上記のコードは、新しいドキュメントを追加するrestaurantsコレクション。ドキュメントデータは、プレーンなJavaScriptオブジェクトから取得されます。我々は最初のクラウドFirestoreコレクションへの参照を取得することによってこれを行うrestaurants 、その後addデータをINGの「。

レストランを追加しましょう!

  1. ブラウザでFriendlyEatsアプリに戻り、更新します。
  2. クリックモックデータを追加します

アプリは自動的に、あなたの呼び出し、レストランオブジェクトのランダムなセットを生成しますaddRestaurant機能を。我々はまだデータ(コードラボの次のセクション)を検索実装する必要があるためしかし、あなたはまだあなたの実際のWebアプリでデータが表示されません

あなたが移動すると、クラウドFirestoreタブものの、Firebaseコンソールで、あなたは今では新しいドキュメントが表示されるはずrestaurantsコレクション!

img6.png

おめでとうございます。WebアプリからCloudFirestoreにデータを書き込んだところです。

次のセクションでは、CloudFirestoreからデータを取得してアプリに表示する方法を学習します。

7. CloudFirestoreからのデータを表示します

このセクションでは、CloudFirestoreからデータを取得してアプリに表示する方法を学習します。 2つの重要なステップは、クエリの作成とスナップショットリスナーの追加です。このリスナーは、クエリに一致するすべての既存のデータについて通知され、リアルタイムで更新を受け取ります。

まず、デフォルトのフィルタリングされていないレストランのリストを提供するクエリを作成しましょう。

  1. ファイルに戻るscripts/FriendlyEats.Data.js
  2. 機能の検索FriendlyEats.prototype.getAllRestaurants
  3. 関数全体を次のコードに置き換えます。

FriendlyEats.Data.js

FriendlyEats.prototype.getAllRestaurants = function(renderer) {
  var query = firebase.firestore()
      .collection('restaurants')
      .orderBy('avgRating', 'desc')
      .limit(50);

  this.getDocumentsInQuery(query, renderer);
};

上記のコードでは、名前のトップレベルのコレクションから、50軒のレストランまで取得するクエリ構築restaurants (現在はすべてゼロ)平均の評価によって順序付けられ、。私たちは、このクエリを宣言した後、我々はそれを渡すgetDocumentsInQuery()ロードとデータをレンダリングする責任がある方法。

これを行うには、スナップショットリスナーを追加します。

  1. ファイルに戻るscripts/FriendlyEats.Data.js
  2. 機能の検索FriendlyEats.prototype.getDocumentsInQuery
  3. 関数全体を次のコードに置き換えます。

FriendlyEats.Data.js

FriendlyEats.prototype.getDocumentsInQuery = function(query, renderer) {
  query.onSnapshot(function(snapshot) {
    if (!snapshot.size) return renderer.empty(); // Display "There are no restaurants".

    snapshot.docChanges().forEach(function(change) {
      if (change.type === 'removed') {
        renderer.remove(change.doc);
      } else {
        renderer.display(change.doc);
      }
    });
  });
};

上記のコードでは、 query.onSnapshotクエリの結果に変更がありますたびにそのコールバックをトリガします。

  • 全体の意味-最初の時間は、コールバックは、クエリの結果セット全体でトリガーされるrestaurantsクラウドFirestoreからコレクションを。その後に、すべての個々の文書を渡しrenderer.display機能。
  • 文書が削除されると、 change.typeに等しいremoved 。したがって、この場合、UIからレストランを削除する関数を呼び出します。

両方のメソッドを実装したので、アプリを更新し、Firebaseコンソールで以前に表示したレストランがアプリに表示されることを確認します。このセクションを正常に完了すると、アプリはCloudFirestoreでデータの読み取りと書き込みを行うようになります。

レストランのリストが変更されると、このリスナーは自動的に更新され続けます。 Firebaseコンソールにアクセスして、レストランを手動で削除するか、名前を変更してみてください。変更内容がすぐにサイトに表示されます。

img5.png

8. Get()データ

これまでのところ、我々は、使用方法を示してきましたonSnapshotリアルタイムに更新を取得します。しかし、それは必ずしも私たちが望んでいることではありません。データを1回だけフェッチする方が理にかなっている場合があります。

ユーザーがアプリ内の特定のレストランをクリックしたときにトリガーされるメソッドを実装する必要があります。

  1. あなたのファイルに戻るscripts/FriendlyEats.Data.js
  2. 機能の検索FriendlyEats.prototype.getRestaurant
  3. 関数全体を次のコードに置き換えます。

FriendlyEats.Data.js

FriendlyEats.prototype.getRestaurant = function(id) {
  return firebase.firestore().collection('restaurants').doc(id).get();
};

このメソッドを実装すると、各レストランのページを表示できるようになります。リスト内のレストランをクリックするだけで、レストランの詳細ページが表示されます。

img1.png

現時点では、後でコードラボで評価の追加を実装する必要があるため、評価を追加することはできません。

9.データの並べ替えとフィルタリング

現在、私たちのアプリはレストランのリストを表示していますが、ユーザーがニーズに基づいてフィルタリングする方法はありません。このセクションでは、CloudFirestoreの高度なクエリを使用してフィルタリングを有効にします。

ここでは、すべてのフェッチするための単純なクエリの例ですDim Sumレストラン:

var filteredQuery = query.where('category', '==', 'Dim Sum')

その名前が示すように、 where()メソッドは、クエリのダウンロードフィールド、我々が設定された制限を満たすコレクションのメンバーのみを行います。この場合、それはレストランのみダウンロードしますcategoryあるDim Sum

私たちのアプリでは、ユーザーは複数のフィルターをチェーンして、「サンフランシスコのピザ」や「人気順に並べられたロサンゼルスのシーフード」などの特定のクエリを作成できます。

ユーザーが選択した複数の基準に基づいてレストランをフィルタリングするクエリを作成するメソッドを作成します。

  1. あなたのファイルに戻るscripts/FriendlyEats.Data.js
  2. 機能の検索FriendlyEats.prototype.getFilteredRestaurants
  3. 関数全体を次のコードに置き換えます。

FriendlyEats.Data.js

FriendlyEats.prototype.getFilteredRestaurants = function(filters, renderer) {
  var query = firebase.firestore().collection('restaurants');

  if (filters.category !== 'Any') {
    query = query.where('category', '==', filters.category);
  }

  if (filters.city !== 'Any') {
    query = query.where('city', '==', filters.city);
  }

  if (filters.price !== 'Any') {
    query = query.where('price', '==', filters.price.length);
  }

  if (filters.sort === 'Rating') {
    query = query.orderBy('avgRating', 'desc');
  } else if (filters.sort === 'Reviews') {
    query = query.orderBy('numRatings', 'desc');
  }

  this.getDocumentsInQuery(query, renderer);
};

上記のコードは、複数の追加whereのフィルタおよび単一orderBy句は、ユーザの入力に基づいて複合クエリを構築します。クエリは、ユーザーの要件に一致するレストランのみを返すようになりました。

ブラウザでFriendlyEatsアプリを更新し、価格、都市、カテゴリでフィルタリングできることを確認します。テスト中、ブラウザのJavaScriptコンソールに次のようなエラーが表示されます。

The query requires an index. You can create it here: https://console.firebase.google.com/project/.../database/firestore/indexes?create_index=...

これらのエラーは、CloudFirestoreがほとんどの複合クエリにインデックスを必要とするためです。クエリにインデックスを要求することで、CloudFirestoreを大規模に高速に保つことができます。

エラーメッセージからリンクを開くと、Firebaseコンソールでインデックス作成UIが自動的に開き、正しいパラメータが入力されます。次のセクションでは、このアプリケーションに必要なインデックスを作成してデプロイします。

10.インデックスをデプロイします

アプリ内のすべてのパスを調べて各インデックス作成リンクをたどりたくない場合は、FirebaseCLIを使用して一度に多くのインデックスを簡単にデプロイできます。

  1. あなたのアプリのダウンロードローカルディレクトリでは、あなたは見つけるfirestore.indexes.jsonファイルを。

このファイルには、フィルターのすべての可能な組み合わせに必要なすべてのインデックスが記述されています。

firestore.indexes.json

{
 "indexes": [
   {
     "collectionGroup": "restaurants",
     "queryScope": "COLLECTION",
     "fields": [
       { "fieldPath": "city", "order": "ASCENDING" },
       { "fieldPath": "avgRating", "order": "DESCENDING" }
     ]
   },

   ...

 ]
}
  1. 次のコマンドを使用して、これらのインデックスを展開します。
firebase deploy --only firestore:indexes

数分後、インデックスが有効になり、エラーメッセージが消えます。

11.トランザクションにデータを書き込む

このセクションでは、ユーザーがレストランにレビューを送信する機能を追加します。これまでのところ、すべての書き込みはアトミックで比較的単純です。それらのいずれかがエラーになった場合は、ユーザーに再試行するように求めるだけであるか、アプリが自動的に書き込みを再試行します。

私たちのアプリには、レストランの評価を追加したい多くのユーザーがいるため、複数の読み取りと書き込みを調整する必要があります。まず、自身が持っているレビューを提出する、そしてレストランの格付けcountaverage rating必要が更新されます。これらの一方が失敗し、もう一方が失敗しない場合、データベースのある部分のデータが別の部分のデータと一致しないという一貫性のない状態になります。

幸い、Cloud Firestoreは、単一のアトミック操作で複数の読み取りと書き込みを実行できるトランザクション機能を提供し、データの一貫性を維持します。

  1. あなたのファイルに戻るscripts/FriendlyEats.Data.js
  2. 機能の検索FriendlyEats.prototype.addRating
  3. 関数全体を次のコードに置き換えます。

FriendlyEats.Data.js

FriendlyEats.prototype.addRating = function(restaurantID, rating) {
  var collection = firebase.firestore().collection('restaurants');
  var document = collection.doc(restaurantID);
  var newRatingDocument = document.collection('ratings').doc();

  return firebase.firestore().runTransaction(function(transaction) {
    return transaction.get(document).then(function(doc) {
      var data = doc.data();

      var newAverage =
          (data.numRatings * data.avgRating + rating.rating) /
          (data.numRatings + 1);

      transaction.update(document, {
        numRatings: data.numRatings + 1,
        avgRating: newAverage
      });
      return transaction.set(newRatingDocument, rating);
    });
  });
};

上記のブロックでは、数値の値を更新するトランザクションをトリガavgRatingnumRatingsレストラン文書に。同時に、私たちは、新しい追加ratingratingsサブコレクションを。

12.データを保護します

このコードラボの冒頭で、アプリのセキュリティルールを設定して、データベースを読み取りまたは書き込みに対して完全に開きます。実際のアプリケーションでは、望ましくないデータへのアクセスや変更を防ぐために、よりきめ細かいルールを設定する必要があります。

  1. Firebaseコンソールの[ビルド]セクションで、Firestoreデータベース]クリックします。
  2. クラウドFirestoreセクションのルール]タブをクリックします(または、ここをクリックし、直接そこに行きます)。
  3. 次のルールでのデフォルト値を置き換え、[公開]をクリックします。

firestore.rules

rules_version = '2';
service cloud.firestore {

  // Determine if the value of the field "key" is the same
  // before and after the request.
  function unchanged(key) {
    return (key in resource.data) 
      && (key in request.resource.data) 
      && (resource.data[key] == request.resource.data[key]);
  }

  match /databases/{database}/documents {
    // Restaurants:
    //   - Authenticated user can read
    //   - Authenticated user can create/update (for demo purposes only)
    //   - Updates are allowed if no fields are added and name is unchanged
    //   - Deletes are not allowed (default)
    match /restaurants/{restaurantId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null;
      allow update: if request.auth != null
                    && (request.resource.data.keys() == resource.data.keys()) 
                    && unchanged("name");
      
      // Ratings:
      //   - Authenticated user can read
      //   - Authenticated user can create if userId matches
      //   - Deletes and updates are not allowed (default)
      match /ratings/{ratingId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;
      }
    }
  }
}

これらのルールは、クライアントが安全な変更のみを行うことを保証するためにアクセスを制限します。例えば:

  • レストランのドキュメントを更新すると、評価のみが変更され、名前やその他の不変のデータは変更されません。
  • レーティングは、ユーザーIDがサインインしたユーザーと一致する場合にのみ作成できます。これにより、なりすましが防止されます。

Firebaseコンソールを使用する代わりに、FirebaseCLIを使用してルールをFirebaseプロジェクトにデプロイできます。 firestore.rulesの作業ディレクトリ内のファイルは、すでに上記のルールが含まれています。 (Firebaseコンソールを使用するのではなく)ローカルファイルシステムからこれらのルールをデプロイするには、次のコマンドを実行します。

firebase deploy --only firestore:rules

13.結論

このコードラボでは、Cloud Firestoreを使用して基本的および高度な読み取りと書き込みを実行する方法と、セキュリティルールを使用してデータアクセスを保護する方法を学習しました。あなたには、完全な解決策を見つけることができますクイックスタート-jsのリポジトリ

Cloud Firestoreの詳細については、次のリソースにアクセスしてください。