1。概要
目標
このコードラボでは、 CloudFirestoreを利用したレストランおすすめのウェブアプリを作成します。
あなたが学ぶこと
- WebアプリからCloudFirestoreへのデータの読み取りと書き込み
- CloudFirestoreデータの変更をリアルタイムで聞く
- Firebase認証とセキュリティルールを使用してCloudFirestoreデータを保護します
- 複雑なCloudFirestoreクエリを作成する
必要なもの
このコードラボを開始する前に、次のものがインストールされていることを確認してください。
2.Firebaseプロジェクトを作成して設定します
Firebaseプロジェクトを作成する
- Firebaseコンソールで、[プロジェクトの追加]をクリックし、FirebaseプロジェクトにFriendlyEatsという名前を付けます。
FirebaseプロジェクトのプロジェクトIDを覚えておいてください。
- [プロジェクトの作成]をクリックします。
構築するアプリケーションは、ウェブ上で利用可能ないくつかのFirebaseサービスを使用します。
- ユーザーを簡単に特定するためのFirebase認証
- 構造化データをクラウドに保存し、データが更新されたときに即座に通知を受け取るCloudFirestore
- 静的アセットをホストして提供するFirebaseHosting
この特定のコードラボでは、FirebaseHostingを既に構成しています。ただし、FirebaseAuthとCloudFirestoreの場合は、Firebaseコンソールを使用したサービスの構成と有効化について説明します。
匿名認証を有効にする
このコードラボでは認証の焦点はありませんが、アプリに何らかの形式の認証を含めることが重要です。匿名ログインを使用します。つまり、ユーザーはプロンプトが表示されずにサイレントサインインします。
匿名ログインを有効にする必要があります。
- Firebaseコンソールで、左側のナビゲーションにある[ビルド]セクションを見つけます。
- [認証]をクリックし、[サインイン方法]タブをクリックします(または、ここをクリックして直接アクセスします)。
- 匿名サインインプロバイダーを有効にして、[保存]をクリックします。
これにより、ユーザーがWebアプリにアクセスしたときに、アプリケーションがサイレントにサインインできるようになります。詳細については、匿名認証のドキュメントをお読みください。
CloudFirestoreを有効にする
このアプリはCloudFirestoreを使用して、レストランの情報と評価を保存および受信します。
CloudFirestoreを有効にする必要があります。 Firebaseコンソールの[ビルド]セクションで、[ Firestoreデータベース]をクリックします。 CloudFirestoreペインでデータベースの作成をクリックします。
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 friendlyeats-web
ディレクトリに複製されているはずです。今後は、必ず次のディレクトリからすべてのコマンドを実行してください。
cd friendlyeats-web
スターターアプリをインポートする
IDE(WebStorm、Atom、Sublime、Visual Studio Code ...)を使用して、📁friendlyeats friendlyeats-web
ディレクトリを開くかインポートします。このディレクトリには、まだ機能していないレストラン推奨アプリで構成されるcodelabの開始コードが含まれています。このコードラボ全体で機能するようにするため、すぐにそのディレクトリのコードを編集する必要があります。
4.Firebaseコマンドラインインターフェースをインストールします
Firebaseコマンドラインインターフェース(CLI)を使用すると、ウェブアプリをローカルで提供し、ウェブアプリをFirebaseHostingにデプロイできます。
- 次のnpmコマンドを実行してCLIをインストールします。
npm -g install firebase-tools
- 次のコマンドを実行して、CLIが正しくインストールされていることを確認します。
firebase --version
FirebaseCLIのバージョンがv7.4.0以降であることを確認してください。
- 次のコマンドを実行して、FirebaseCLIを承認します。
firebase login
アプリのローカルディレクトリとファイルからFirebaseHostingのアプリの構成を取得するように、ウェブアプリテンプレートを設定しました。ただし、これを行うには、アプリをFirebaseプロジェクトに関連付ける必要があります。
- コマンドラインがアプリのローカルディレクトリにアクセスしていることを確認してください。
- 次のコマンドを実行して、アプリをFirebaseプロジェクトに関連付けます。
firebase use --add
- プロンプトが表示されたら、プロジェクトIDを選択し、Firebaseプロジェクトにエイリアスを指定します。
エイリアスは、複数の環境(本番環境、ステージングなど)がある場合に役立ちます。ただし、このコードラボでは、 default
のエイリアスを使用してみましょう。
- コマンドラインの残りの指示に従います。
5.ローカルサーバーを実行します
私たちは実際に私たちのアプリの作業を開始する準備ができています!アプリをローカルで実行しましょう!
- 次のFirebaseCLIコマンドを実行します。
firebase emulators:start --only hosting
- コマンドラインに次の応答が表示されます。
hosting: Local server: http://localhost:5000
アプリをローカルで提供するためにFirebaseHostingエミュレーターを使用しています。これで、Webアプリがhttp:// localhost:5000から利用できるようになります。
- http:// localhost:5000でアプリを開きます。
Firebaseプロジェクトに接続されているFriendlyEatsのコピーが表示されます。
アプリは自動的にFirebaseプロジェクトに接続し、匿名ユーザーとしてサイレントサインインします。
6.CloudFirestoreにデータを書き込みます
このセクションでは、アプリのUIにデータを入力できるように、CloudFirestoreにデータを書き込みます。これはFirebaseコンソールを介して手動で実行できますが、基本的なCloudFirestoreの書き込みを示すためにアプリ自体で実行します。
データ・モデル
Firestoreデータは、コレクション、ドキュメント、フィールド、およびサブコレクションに分割されます。各レストランは、 restaurants
と呼ばれるトップレベルのコレクションにドキュメントとして保存されます。
後で、各レビューを各レストランのratings
と呼ばれるサブコレクションに保存します。
Firestoreにレストランを追加する
私たちのアプリの主なモデルオブジェクトはレストランです。レストランのドキュメントをrestaurants
のコレクションに追加するコードを書いてみましょう。
- ダウンロードしたファイルから、
scripts/FriendlyEats.Data.js
を開きます。 - 関数
FriendlyEats.prototype.addRestaurant
を見つけます。 - 関数全体を次のコードに置き換えます。
FriendlyEats.Data.js
FriendlyEats.prototype.addRestaurant = function(data) { var collection = firebase.firestore().collection('restaurants'); return collection.add(data); };
上記のコードは、 restaurants
コレクションに新しいドキュメントを追加します。ドキュメントデータは、プレーンなJavaScriptオブジェクトから取得されます。これを行うには、最初にCloud Firestoreコレクションrestaurants
への参照を取得してから、データをadd
します。
レストランを追加しましょう!
- ブラウザでFriendlyEatsアプリに戻り、更新します。
- [モックデータの追加]をクリックします。
アプリは自動的にレストランオブジェクトのランダムなセットを生成し、次にaddRestaurant
関数を呼び出します。ただし、データの取得を実装する必要があるため(コードラボの次のセクション) 、実際のWebアプリにはまだデータが表示されません。
ただし、Firebaseコンソールの[Cloud Firestore]タブに移動すると、 restaurants
コレクションに新しいドキュメントが表示されるはずです。
おめでとうございます。WebアプリからCloudFirestoreにデータを書き込んだところです。
次のセクションでは、CloudFirestoreからデータを取得してアプリに表示する方法を学習します。
7.CloudFirestoreからのデータを表示します
このセクションでは、CloudFirestoreからデータを取得してアプリに表示する方法を学習します。 2つの重要なステップは、クエリの作成とスナップショットリスナーの追加です。このリスナーには、クエリに一致するすべての既存のデータが通知され、リアルタイムで更新を受信します。
まず、デフォルトのフィルタリングされていないレストランのリストを提供するクエリを作成しましょう。
- ファイル
scripts/FriendlyEats.Data.js
に戻ります。 - 関数
FriendlyEats.prototype.getAllRestaurants
を見つけます。 - 関数全体を次のコードに置き換えます。
FriendlyEats.Data.js
FriendlyEats.prototype.getAllRestaurants = function(renderer) { var query = firebase.firestore() .collection('restaurants') .orderBy('avgRating', 'desc') .limit(50); this.getDocumentsInQuery(query, renderer); };
上記のコードでは、 restaurants
という名前のトップレベルのコレクションから最大50のレストランを取得するクエリを作成します。これらのレストランは、平均評価(現在はすべてゼロ)の順に並べられています。このクエリを宣言した後、データのロードとレンダリングを担当するgetDocumentsInQuery()
メソッドに渡します。
これを行うには、スナップショットリスナーを追加します。
- ファイル
scripts/FriendlyEats.Data.js
に戻ります。 - 関数
FriendlyEats.prototype.getDocumentsInQuery
を見つけます。 - 関数全体を次のコードに置き換えます。
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
は、クエリの結果が変更されるたびにコールバックをトリガーします。
- 初めて、コールバックはクエリの結果セット全体、つまりCloudFirestoreの
restaurants
コレクション全体でトリガーされます。次に、すべての個々のドキュメントをrenderer.display
関数に渡します。 - ドキュメントが削除されると、change.typeは
change.type
と等しくなりremoved
。したがって、この場合、UIからレストランを削除する関数を呼び出します。
両方のメソッドを実装したので、アプリを更新して、Firebaseコンソールで以前に表示したレストランがアプリに表示されることを確認します。このセクションを正常に完了すると、アプリはCloudFirestoreを使用してデータの読み取りと書き込みを行うようになります。
レストランのリストが変更されると、このリスナーは自動的に更新され続けます。 Firebaseコンソールにアクセスして、レストランを手動で削除するか、名前を変更してみてください。変更内容がすぐにサイトに表示されます。
8. Get()データ
これまで、 onSnapshot
を使用して更新をリアルタイムで取得する方法を示してきました。しかし、それは必ずしも私たちが望んでいることではありません。データを1回だけフェッチする方が理にかなっている場合があります。
ユーザーがアプリ内の特定のレストランをクリックしたときにトリガーされるメソッドを実装する必要があります。
- ファイル
scripts/FriendlyEats.Data.js
に戻ります。 - 関数
FriendlyEats.prototype.getRestaurant
を見つけます。 - 関数全体を次のコードに置き換えます。
FriendlyEats.Data.js
FriendlyEats.prototype.getRestaurant = function(id) { return firebase.firestore().collection('restaurants').doc(id).get(); };
このメソッドを実装すると、各レストランのページを表示できるようになります。リスト内のレストランをクリックするだけで、レストランの詳細ページが表示されます。
現時点では、後でコードラボで評価の追加を実装する必要があるため、評価を追加することはできません。
9.データの並べ替えとフィルタリング
現在、私たちのアプリはレストランのリストを表示していますが、ユーザーがニーズに基づいてフィルタリングする方法はありません。このセクションでは、CloudFirestoreの高度なクエリを使用してフィルタリングを有効にします。
すべてのDim Sum
レストランを取得するための簡単なクエリの例を次に示します。
var filteredQuery = query.where('category', '==', 'Dim Sum')
その名前が示すように、 where()
メソッドは、フィールドが設定された制限を満たすコレクションのメンバーのみをクエリにダウンロードさせます。この場合、 category
がDim Sum
であるレストランのみがダウンロードされます。
このアプリでは、ユーザーは複数のフィルターをチェーンして、「サンフランシスコのピザ」や「ロサンゼルスのシーフードの人気順」などの特定のクエリを作成できます。
ユーザーが選択した複数の基準に基づいてレストランをフィルタリングするクエリを作成するメソッドを作成します。
- ファイル
scripts/FriendlyEats.Data.js
に戻ります。 - 関数
FriendlyEats.prototype.getFilteredRestaurants
を見つけます。 - 関数全体を次のコードに置き換えます。
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
フィルターと1つの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を使用して一度に多くのインデックスを簡単にデプロイできます。
- アプリのダウンロードされたローカルディレクトリに、
firestore.indexes.json
ファイルがあります。
このファイルには、フィルターのすべての可能な組み合わせに必要なすべてのインデックスが記述されています。
firestore.indexes.json
{ "indexes": [ { "collectionGroup": "restaurants", "queryScope": "COLLECTION", "fields": [ { "fieldPath": "city", "order": "ASCENDING" }, { "fieldPath": "avgRating", "order": "DESCENDING" } ] }, ... ] }
- 次のコマンドを使用して、これらのインデックスを展開します。
firebase deploy --only firestore:indexes
数分後、インデックスが有効になり、エラーメッセージが消えます。
11.トランザクションにデータを書き込む
このセクションでは、ユーザーがレストランにレビューを送信する機能を追加します。これまでのところ、すべての書き込みはアトミックで比較的単純です。それらのいずれかがエラーになった場合は、ユーザーに再試行するように促すか、アプリが自動的に書き込みを再試行します。
私たちのアプリには、レストランの評価を追加したい多くのユーザーがいるため、複数の読み取りと書き込みを調整する必要があります。最初にレビュー自体を送信する必要があり、次にレストランの評価count
とaverage rating
を更新する必要があります。これらの一方が失敗し、もう一方が失敗しない場合、データベースのある部分のデータが別の部分のデータと一致しないという一貫性のない状態になります。
幸い、Cloud Firestoreは、単一のアトミック操作で複数の読み取りと書き込みを実行できるトランザクション機能を提供し、データの一貫性を維持します。
- ファイル
scripts/FriendlyEats.Data.js
に戻ります。 - 関数
FriendlyEats.prototype.addRating
を見つけます。 - 関数全体を次のコードに置き換えます。
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); }); }); };
上記のブロックでは、レストランドキュメントのavgRating
とnumRatings
の数値を更新するトランザクションをトリガーします。同時に、新しいrating
をratings
サブコレクションに追加します。
12.データを保護します
このコードラボの冒頭で、データベースをすべての読み取りまたは書き込みに対して完全に開くようにアプリのセキュリティルールを設定しました。実際のアプリケーションでは、望ましくないデータアクセスや変更を防ぐために、よりきめ細かいルールを設定する必要があります。
- Firebaseコンソールの[ビルド]セクションで、[ Firestoreデータベース]をクリックします。
- [Cloud Firestore]セクションの[ルール]タブをクリックします(または、ここをクリックして直接そこに移動します)。
- デフォルトを次のルールに置き換えてから、[公開]をクリックします。
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を使用して基本および高度な読み取りと書き込みを実行する方法と、セキュリティルールを使用してデータアクセスを保護する方法を学習しました。完全なソリューションは、 quickstarts-jsリポジトリにあります。
Cloud Firestoreの詳細については、次のリソースにアクセスしてください。