Atelier de programmation Web Cloud Firestore

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

1. Vue d'ensemble

Buts

Dans cet atelier de programmation, vous allez créer une application Web de recommandation de restaurants optimisée par Cloud Firestore .

img5.png

Ce que vous apprendrez

  • Lire et écrire des données dans Cloud Firestore à partir d'une application Web
  • Écoutez les modifications des données Cloud Firestore en temps réel
  • Utilisez Firebase Authentication et les règles de sécurité pour sécuriser les données Cloud Firestore
  • Écrire des requêtes Cloud Firestore complexes

Ce dont vous aurez besoin

Avant de commencer cet atelier de programmation, assurez-vous d'avoir installé :

2. Créer et configurer un projet Firebase

Créer un projet Firebase

  1. Dans la console Firebase , cliquez sur Ajouter un projet , puis nommez le projet Firebase FriendlyEats .

N'oubliez pas l'ID de projet de votre projet Firebase.

  1. Cliquez sur Créer un projet .

L'application que nous allons construire utilise quelques services Firebase disponibles sur le web :

  • Firebase Authentication pour identifier facilement vos utilisateurs
  • Cloud Firestore pour enregistrer des données structurées sur le Cloud et recevoir une notification instantanée lorsque les données sont mises à jour
  • Firebase Hosting pour héberger et servir vos ressources statiques

Pour cet atelier de programmation spécifique, nous avons déjà configuré l'hébergement Firebase. Cependant, pour Firebase Auth et Cloud Firestore, nous vous guiderons tout au long de la configuration et de l'activation des services à l'aide de la console Firebase.

Activer l'authentification anonyme

Bien que l'authentification ne soit pas au centre de cet atelier de programmation, il est important d'avoir une forme d'authentification dans notre application. Nous utiliserons la connexion anonyme - ce qui signifie que l'utilisateur sera connecté en silence sans y être invité.

Vous devrez activer la connexion anonyme.

  1. Dans la console Firebase, localisez la section Build dans la barre de navigation de gauche.
  2. Cliquez sur Authentification , puis cliquez sur l'onglet Méthode de connexion (ou cliquez ici pour y accéder directement).
  3. Activez le fournisseur de connexion anonyme , puis cliquez sur Enregistrer .

img7.png

Cela permettra à l'application de connecter silencieusement vos utilisateurs lorsqu'ils accèdent à l'application Web. N'hésitez pas à lire la documentation sur l'authentification anonyme pour en savoir plus.

Activer Cloud Firestore

L'application utilise Cloud Firestore pour enregistrer et recevoir des informations et des évaluations sur les restaurants.

Vous devrez activer Cloud Firestore. Dans la section Build de la console Firebase, cliquez sur Firestore Database . Cliquez sur Créer une base de données dans le volet Cloud Firestore.

L'accès aux données dans Cloud Firestore est contrôlé par des règles de sécurité. Nous parlerons plus en détail des règles plus tard dans cet atelier de programmation, mais nous devons d'abord définir quelques règles de base sur nos données pour commencer. Dans l' onglet Règles de la console Firebase, ajoutez les règles suivantes, puis cliquez sur Publier .

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;
    }
  }
}

Les règles ci-dessus limitent l'accès aux données aux utilisateurs connectés, ce qui empêche les utilisateurs non authentifiés de lire ou d'écrire. C'est mieux que d'autoriser l'accès public mais c'est encore loin d'être sécurisé, nous améliorerons ces règles plus tard dans le codelab.

3. Obtenez l'exemple de code

Clonez le dépôt GitHub à partir de la ligne de commande :

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

L'exemple de code aurait dû être cloné dans le répertoire 📁 friendlyeats-web . À partir de maintenant, assurez-vous d'exécuter toutes vos commandes à partir de ce répertoire :

cd friendlyeats-web

Importer l'application de démarrage

A l'aide de votre IDE (WebStorm, Atom, Sublime, Visual Studio Code...) ouvrez ou importez le répertoire 📁 friendlyeats-web . Ce répertoire contient le code de démarrage du codelab qui consiste en une application de recommandation de restaurants pas encore fonctionnelle. Nous le rendrons fonctionnel tout au long de cet atelier de programmation, vous devrez donc bientôt modifier le code dans ce répertoire.

4. Installez l'interface de ligne de commande Firebase

L'interface de ligne de commande Firebase (CLI) vous permet de servir votre application Web localement et de déployer votre application Web sur Firebase Hosting.

  1. Installez la CLI en exécutant la commande npm suivante :
npm -g install firebase-tools
  1. Vérifiez que la CLI a été correctement installée en exécutant la commande suivante :
firebase --version

Assurez-vous que la version de la CLI Firebase est v7.4.0 ou ultérieure.

  1. Autorisez la CLI Firebase en exécutant la commande suivante :
firebase login

Nous avons configuré le modèle d'application Web pour extraire la configuration de votre application pour l'hébergement Firebase à partir du répertoire et des fichiers locaux de votre application. Mais pour ce faire, nous devons associer votre application à votre projet Firebase.

  1. Assurez-vous que votre ligne de commande accède au répertoire local de votre application.
  2. Associez votre application à votre projet Firebase en exécutant la commande suivante :
firebase use --add
  1. Lorsque vous y êtes invité, sélectionnez votre ID de projet , puis attribuez un alias à votre projet Firebase.

Un alias est utile si vous avez plusieurs environnements (production, staging, etc.). Cependant, pour cet atelier de programmation, utilisons simplement l'alias de default .

  1. Suivez les instructions restantes dans votre ligne de commande.

5. Exécutez le serveur local

Nous sommes prêts à commencer à travailler sur notre application ! Exécutons notre application localement !

  1. Exécutez la commande CLI Firebase suivante :
firebase emulators:start --only hosting
  1. Votre ligne de commande doit afficher la réponse suivante :
hosting: Local server: http://localhost:5000

Nous utilisons l'émulateur Firebase Hosting pour servir notre application localement. L'application Web devrait maintenant être disponible à partir de http://localhost:5000 .

  1. Ouvrez votre application à http://localhost:5000 .

Vous devriez voir votre copie de FriendlyEats qui a été connectée à votre projet Firebase.

L'application s'est automatiquement connectée à votre projet Firebase et vous a connecté en mode silencieux en tant qu'utilisateur anonyme.

img2.png

6. Écrire des données dans Cloud Firestore

Dans cette section, nous allons écrire des données dans Cloud Firestore afin de pouvoir remplir l'interface utilisateur de l'application. Cela peut être fait manuellement via la console Firebase , mais nous le ferons dans l'application elle-même pour démontrer une écriture de base Cloud Firestore.

Modèle de données

Les données Firestore sont divisées en collections, documents, champs et sous-collections. Nous stockerons chaque restaurant en tant que document dans une collection de niveau supérieur appelée restaurants .

img3.png

Plus tard, nous stockerons chaque avis dans une sous-collection appelée ratings sous chaque restaurant.

img4.png

Ajouter des restaurants à Firestore

L'objet modèle principal de notre application est un restaurant. Écrivons du code qui ajoute un document de restaurants à la collection de restaurants.

  1. À partir de vos fichiers téléchargés, ouvrez scripts/FriendlyEats.Data.js .
  2. Trouvez la fonction FriendlyEats.prototype.addRestaurant .
  3. Remplacez la fonction entière par le code suivant.

FriendlyEats.Data.js

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

Le code ci-dessus ajoute un nouveau document à la collection restaurants . Les données du document proviennent d'un objet JavaScript simple. Pour ce faire, nous obtenons d'abord une référence à une collection de restaurants Cloud Firestore, puis nous add les données.

Ajoutons des restaurants !

  1. Revenez à votre application FriendlyEats dans votre navigateur et actualisez-la.
  2. Cliquez sur Ajouter des données fictives .

L'application générera automatiquement un ensemble aléatoire d'objets restaurants, puis appellera votre fonction addRestaurant . Cependant, vous ne verrez pas encore les données dans votre application Web réelle, car nous devons encore implémenter la récupération des données (la section suivante du codelab).

Si vous accédez à l' onglet Cloud Firestore dans la console Firebase, vous devriez maintenant voir de nouveaux documents dans la collection de restaurants !

img6.png

Félicitations, vous venez d'écrire des données dans Cloud Firestore à partir d'une application Web !

Dans la section suivante, vous apprendrez à récupérer des données de Cloud Firestore et à les afficher dans votre application.

7. Afficher les données de Cloud Firestore

Dans cette section, vous apprendrez à récupérer des données de Cloud Firestore et à les afficher dans votre application. Les deux étapes clés sont la création d'une requête et l'ajout d'un écouteur d'instantané. Cet écouteur sera informé de toutes les données existantes correspondant à la requête et recevra des mises à jour en temps réel.

Tout d'abord, construisons la requête qui servira la liste de restaurants non filtrée par défaut.

  1. Revenez au fichier scripts/FriendlyEats.Data.js .
  2. Trouvez la fonction FriendlyEats.prototype.getAllRestaurants .
  3. Remplacez la fonction entière par le code suivant.

FriendlyEats.Data.js

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

  this.getDocumentsInQuery(query, renderer);
};

Dans le code ci-dessus, nous construisons une requête qui récupère jusqu'à 50 restaurants de la collection de niveau supérieur nommée restaurants , qui sont classés par note moyenne (actuellement tous à zéro). Après avoir déclaré cette requête, nous la transmettons à la méthode getDocumentsInQuery() qui est responsable du chargement et du rendu des données.

Nous allons le faire en ajoutant un écouteur d'instantané.

  1. Revenez au fichier scripts/FriendlyEats.Data.js .
  2. Trouvez la fonction FriendlyEats.prototype.getDocumentsInQuery .
  3. Remplacez la fonction entière par le code suivant.

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);
      }
    });
  });
};

Dans le code ci-dessus, query.onSnapshot déclenchera son rappel chaque fois qu'il y a un changement dans le résultat de la requête.

  • La première fois, le rappel est déclenché avec l'ensemble des résultats de la requête, c'est-à-dire l'ensemble de la collection de restaurants de Cloud Firestore. Il transmet ensuite tous les documents individuels à la fonction renderer.display .
  • Lorsqu'un document est removed , change.type est égal à remove . Donc, dans ce cas, nous appellerons une fonction qui supprime le restaurant de l'interface utilisateur.

Maintenant que nous avons implémenté les deux méthodes, actualisez l'application et vérifiez que les restaurants que nous avons vus précédemment dans la console Firebase sont désormais visibles dans l'application. Si vous avez terminé cette section avec succès, votre application lit et écrit maintenant des données avec Cloud Firestore !

Au fur et à mesure que votre liste de restaurants change, cet écouteur continuera à se mettre à jour automatiquement. Essayez d'accéder à la console Firebase et de supprimer manuellement un restaurant ou de changer son nom - vous verrez les modifications apparaître immédiatement sur votre site !

img5.png

8. Obtenir () les données

Jusqu'à présent, nous avons montré comment utiliser onSnapshot pour récupérer les mises à jour en temps réel ; cependant, ce n'est pas toujours ce que nous voulons. Parfois, il est plus logique de ne récupérer les données qu'une seule fois.

Nous voudrons implémenter une méthode qui se déclenche lorsqu'un utilisateur clique sur un restaurant spécifique dans votre application.

  1. Retournez dans votre fichier scripts/FriendlyEats.Data.js .
  2. Trouvez la fonction FriendlyEats.prototype.getRestaurant .
  3. Remplacez la fonction entière par le code suivant.

FriendlyEats.Data.js

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

Après avoir implémenté cette méthode, vous pourrez afficher les pages de chaque restaurant. Cliquez simplement sur un restaurant dans la liste et vous devriez voir la page de détails du restaurant :

img1.png

Pour l'instant, vous ne pouvez pas ajouter d'évaluations, car nous devons encore implémenter l'ajout d'évaluations ultérieurement dans le laboratoire de programmation.

9. Trier et filtrer les données

Actuellement, notre application affiche une liste de restaurants, mais l'utilisateur n'a aucun moyen de filtrer en fonction de ses besoins. Dans cette section, vous utiliserez les requêtes avancées de Cloud Firestore pour activer le filtrage.

Voici un exemple de requête simple pour récupérer tous les restaurants Dim Sum :

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

Comme son nom l'indique, la méthode where() fera en sorte que notre requête ne télécharge que les membres de la collection dont les champs respectent les restrictions que nous avons définies. Dans ce cas, seuls les restaurants dont category est Dim Sum seront téléchargés.

Dans notre application, l'utilisateur peut enchaîner plusieurs filtres pour créer des requêtes spécifiques, comme "Pizza à San Francisco" ou "Fruits de mer à Los Angeles classés par popularité".

Nous allons créer une méthode qui construit une requête qui filtrera nos restaurants en fonction de multiples critères sélectionnés par nos utilisateurs.

  1. Retournez dans votre fichier scripts/FriendlyEats.Data.js .
  2. Trouvez la fonction FriendlyEats.prototype.getFilteredRestaurants .
  3. Remplacez la fonction entière par le code suivant.

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);
};

Le code ci-dessus ajoute plusieurs filtres where et une seule clause orderBy pour créer une requête composée basée sur l'entrée de l'utilisateur. Notre requête renverra désormais uniquement les restaurants qui correspondent aux besoins de l'utilisateur.

Actualisez votre application FriendlyEats dans votre navigateur, puis vérifiez que vous pouvez filtrer par prix, ville et catégorie. Lors du test, vous verrez des erreurs dans la console JavaScript de votre navigateur qui ressemblent à ceci :

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

Ces erreurs sont dues au fait que Cloud Firestore nécessite des index pour la plupart des requêtes composées. L'exigence d'index sur les requêtes permet à Cloud Firestore de rester rapide à grande échelle.

L'ouverture du lien à partir du message d'erreur ouvrira automatiquement l'interface utilisateur de création d'index dans la console Firebase avec les paramètres corrects renseignés. Dans la section suivante, nous allons écrire et déployer les index nécessaires pour cette application.

10. Déployer les index

Si nous ne voulons pas explorer tous les chemins de notre application et suivre chacun des liens de création d'index, nous pouvons facilement déployer plusieurs index à la fois à l'aide de la CLI Firebase.

  1. Dans le répertoire local téléchargé de votre application, vous trouverez un fichier firestore.indexes.json .

Ce fichier décrit tous les index nécessaires pour toutes les combinaisons possibles de filtres.

firestore.indexes.json

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

   ...

 ]
}
  1. Déployez ces index avec la commande suivante :
firebase deploy --only firestore:indexes

Après quelques minutes, vos index seront actifs et les messages d'erreur disparaîtront.

11. Écrire des données dans une transaction

Dans cette section, nous ajouterons la possibilité pour les utilisateurs de soumettre des avis aux restaurants. Jusqu'à présent, toutes nos écritures ont été atomiques et relativement simples. Si l'un d'entre eux comportait une erreur, nous inviterions probablement simplement l'utilisateur à les réessayer ou notre application réessayerait l'écriture automatiquement.

Notre application aura de nombreux utilisateurs qui souhaitent ajouter une note pour un restaurant, nous devrons donc coordonner plusieurs lectures et écritures. L'avis lui-même doit d'abord être soumis, puis le count d'évaluations et l' average rating du restaurant doivent être mis à jour. Si l'un d'entre eux échoue mais pas l'autre, nous nous retrouvons dans un état incohérent où les données d'une partie de notre base de données ne correspondent pas aux données d'une autre.

Heureusement, Cloud Firestore fournit une fonctionnalité de transaction qui nous permet d'effectuer plusieurs lectures et écritures en une seule opération atomique, garantissant ainsi la cohérence de nos données.

  1. Retournez dans votre fichier scripts/FriendlyEats.Data.js .
  2. Trouvez la fonction FriendlyEats.prototype.addRating .
  3. Remplacez la fonction entière par le code suivant.

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);
    });
  });
};

Dans le bloc ci-dessus, nous déclenchons une transaction pour mettre à jour les valeurs numériques de avgRating et numRatings dans le document du restaurant. En même temps, nous ajoutons la nouvelle rating à la sous-collection d' ratings .

12. Sécurisez vos données

Au début de cet atelier de programmation, nous avons défini les règles de sécurité de notre application pour ouvrir complètement la base de données à toute lecture ou écriture. Dans une application réelle, nous voudrions définir des règles beaucoup plus précises pour empêcher l'accès ou la modification indésirable des données.

  1. Dans la section Build de la console Firebase, cliquez sur Firestore Database .
  2. Cliquez sur l'onglet Règles dans la section Cloud Firestore (ou cliquez ici pour y accéder directement).
  3. Remplacez les valeurs par défaut par les règles suivantes, puis cliquez sur Publier .

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;
      }
    }
  }
}

Ces règles restreignent l'accès pour garantir que les clients n'effectuent que des modifications sécurisées. Par exemple:

  • Les mises à jour d'un document de restaurant ne peuvent modifier que les notes, pas le nom ou toute autre donnée immuable.
  • Les évaluations ne peuvent être créées que si l'ID utilisateur correspond à l'utilisateur connecté, ce qui empêche l'usurpation d'identité.

Au lieu d'utiliser la console Firebase, vous pouvez utiliser la CLI Firebase pour déployer des règles sur votre projet Firebase. Le fichier firestore.rules dans votre répertoire de travail contient déjà les règles ci-dessus. Pour déployer ces règles à partir de votre système de fichiers local (plutôt que d'utiliser la console Firebase), vous devez exécuter la commande suivante :

firebase deploy --only firestore:rules

13.Conclusion

Dans cet atelier de programmation, vous avez appris à effectuer des lectures et des écritures de base et avancées avec Cloud Firestore, ainsi qu'à sécuriser l'accès aux données avec des règles de sécurité. Vous pouvez trouver la solution complète dans le référentiel quickstarts-js .

Pour en savoir plus sur Cloud Firestore, consultez les ressources suivantes :