Laboratorio de código web de Cloud Firestore

1. Información general

Metas

En este laboratorio de código, creará una aplicación web de recomendación de restaurantes con tecnología de Cloud Firestore .

img5.png

lo que aprenderás

  • Leer y escribir datos en Cloud Firestore desde una aplicación web
  • Escuche los cambios en los datos de Cloud Firestore en tiempo real
  • Utilice la autenticación de Firebase y las reglas de seguridad para proteger los datos de Cloud Firestore
  • Escribir consultas complejas de Cloud Firestore

Lo que necesitarás

Antes de iniciar este laboratorio de código, asegúrese de haber instalado:

2. Crea y configura un proyecto de Firebase

Crear un proyecto de Firebase

  1. En la consola de Firebase , haga clic en Agregar proyecto y luego asigne al proyecto de Firebase el nombre FriendlyEats .

Recuerde el ID del proyecto para su proyecto de Firebase.

  1. Haga clic en Crear proyecto .

La aplicación que vamos a construir usa algunos servicios de Firebase disponibles en la web:

  • Firebase Authentication para identificar fácilmente a tus usuarios
  • Cloud Firestore para guardar datos estructurados en la nube y recibir notificaciones instantáneas cuando se actualicen los datos
  • Firebase Hosting para alojar y servir sus activos estáticos

Para este laboratorio de código específico, ya configuramos Firebase Hosting. Sin embargo, para Firebase Auth y Cloud Firestore, lo guiaremos a través de la configuración y habilitación de los servicios mediante Firebase console.

Habilitar autenticación anónima

Aunque la autenticación no es el enfoque de este laboratorio de código, es importante tener alguna forma de autenticación en nuestra aplicación. Usaremos el inicio de sesión anónimo , lo que significa que el usuario iniciará sesión en silencio sin que se le solicite.

Deberá habilitar el inicio de sesión anónimo.

  1. En Firebase console, busque la sección Build en el panel de navegación izquierdo.
  2. Haga clic en Autenticación , luego haga clic en la pestaña Método de inicio de sesión (o haga clic aquí para ir directamente allí).
  3. Habilite el Proveedor de inicio de sesión anónimo , luego haga clic en Guardar .

img7.png

Esto permitirá que la aplicación inicie sesión silenciosamente con sus usuarios cuando accedan a la aplicación web. No dude en leer la documentación de Autenticación anónima para obtener más información.

Habilitar Cloud Firestore

La aplicación utiliza Cloud Firestore para guardar y recibir información y calificaciones de restaurantes.

Deberá habilitar Cloud Firestore. En la sección Crear de la consola Firebase, haga clic en Base de datos de Firestore . Haga clic en Crear base de datos en el panel de Cloud Firestore.

El acceso a los datos en Cloud Firestore está controlado por reglas de seguridad. Hablaremos más sobre las reglas más adelante en este laboratorio de código, pero primero debemos establecer algunas reglas básicas en nuestros datos para comenzar. En la pestaña Reglas de Firebase console, agregue las siguientes reglas y luego haga clic en Publicar .

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

Las reglas anteriores restringen el acceso a los datos a los usuarios que han iniciado sesión, lo que impide que los usuarios no autenticados lean o escriban. Esto es mejor que permitir el acceso público, pero aún está lejos de ser seguro. Mejoraremos estas reglas más adelante en el codelab.

3. Obtenga el código de muestra

Clona el repositorio de GitHub desde la línea de comando:

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

El código de muestra debería haberse clonado en el directorio 📁 friendlyeats-web . De ahora en adelante, asegúrese de ejecutar todos sus comandos desde este directorio:

cd friendlyeats-web

Importar la aplicación de inicio

Usando su IDE (WebStorm, Atom, Sublime, Visual Studio Code...) abra o importe el directorio 📁 friendlyeats-web . Este directorio contiene el código de inicio del laboratorio de código, que consiste en una aplicación de recomendación de restaurantes que aún no funciona. Lo haremos funcional a lo largo de este laboratorio de código, por lo que pronto deberá editar el código en ese directorio.

4. Instalar la interfaz de línea de comandos de Firebase

La interfaz de línea de comandos (CLI) de Firebase le permite servir su aplicación web localmente e implementar su aplicación web en Firebase Hosting.

  1. Instale la CLI ejecutando el siguiente comando npm:
npm -g install firebase-tools
  1. Verifique que la CLI se haya instalado correctamente ejecutando el siguiente comando:
firebase --version

Asegúrese de que la versión de Firebase CLI sea v7.4.0 o posterior.

  1. Autorice Firebase CLI ejecutando el siguiente comando:
firebase login

Configuramos la plantilla de la aplicación web para obtener la configuración de su aplicación para Firebase Hosting desde el directorio y los archivos locales de su aplicación. Pero para hacer esto, necesitamos asociar su aplicación con su proyecto de Firebase.

  1. Asegúrese de que su línea de comando esté accediendo al directorio local de su aplicación.
  2. Asocie su aplicación con su proyecto de Firebase ejecutando el siguiente comando:
firebase use --add
  1. Cuando se le solicite, seleccione su ID de proyecto , luego asigne un alias a su proyecto de Firebase.

Un alias es útil si tiene varios entornos (producción, puesta en escena, etc.). Sin embargo, para este laboratorio de código, solo usemos el alias de default .

  1. Siga las instrucciones restantes en su línea de comando.

5. Ejecute el servidor local

¡Estamos listos para comenzar a trabajar en nuestra aplicación! ¡Ejecutemos nuestra aplicación localmente!

  1. Ejecute el siguiente comando de la CLI de Firebase:
firebase emulators:start --only hosting
  1. Su línea de comando debería mostrar la siguiente respuesta:
hosting: Local server: http://localhost:5000

Usamos el emulador de Firebase Hosting para servir nuestra aplicación localmente. La aplicación web ahora debería estar disponible en http://localhost:5000 .

  1. Abra su aplicación en http://localhost:5000 .

Debería ver su copia de FriendlyEats que se ha conectado a su proyecto de Firebase.

La aplicación se ha conectado automáticamente a su proyecto de Firebase y ha iniciado sesión silenciosamente como un usuario anónimo.

img2.png

6. Escribir datos en Cloud Firestore

En esta sección, escribiremos algunos datos en Cloud Firestore para que podamos completar la interfaz de usuario de la aplicación. Esto se puede hacer manualmente a través de la consola de Firebase , pero lo haremos en la propia aplicación para demostrar una escritura básica de Cloud Firestore.

Modelo de datos

Los datos de Firestore se dividen en colecciones, documentos, campos y subcolecciones. Almacenaremos cada restaurante como un documento en una colección de nivel superior llamada restaurants .

img3.png

Más tarde, almacenaremos cada reseña en una subcolección llamada ratings debajo de cada restaurante.

img4.png

Agregar restaurantes a Firestore

El objeto principal del modelo en nuestra aplicación es un restaurante. Escribamos un código que agregue un documento de restaurante a la colección de restaurants .

  1. Desde sus archivos descargados, abra scripts/FriendlyEats.Data.js .
  2. Encuentra la función FriendlyEats.prototype.addRestaurant .
  3. Reemplace toda la función con el siguiente código.

FriendlyEats.Data.js

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

El código anterior agrega un nuevo documento a la colección de restaurants . Los datos del documento provienen de un objeto JavaScript simple. Hacemos esto obteniendo primero una referencia a una colección de restaurants de Cloud Firestore y luego add los datos.

¡Agreguemos restaurantes!

  1. Vuelva a su aplicación FriendlyEats en su navegador y actualícela.
  2. Haga clic en Agregar datos simulados .

La aplicación generará automáticamente un conjunto aleatorio de objetos de restaurantes y luego llamará a su función addRestaurant . Sin embargo, aún no verá los datos en su aplicación web real porque todavía tenemos que implementar la recuperación de datos (la siguiente sección del laboratorio de código).

Sin embargo, si navega a la pestaña Cloud Firestore en la consola de Firebase, ¡ahora debería ver nuevos documentos en la colección de restaurants !

img6.png

¡Felicitaciones, acaba de escribir datos en Cloud Firestore desde una aplicación web!

En la siguiente sección, aprenderá cómo recuperar datos de Cloud Firestore y mostrarlos en su aplicación.

7. Mostrar datos de Cloud Firestore

En esta sección, aprenderá cómo recuperar datos de Cloud Firestore y mostrarlos en su aplicación. Los dos pasos clave son crear una consulta y agregar un detector de instantáneas. Este oyente será notificado de todos los datos existentes que coincidan con la consulta y recibirá actualizaciones en tiempo real.

Primero, construyamos la consulta que servirá la lista predeterminada de restaurantes sin filtrar.

  1. Vuelva al archivo scripts/FriendlyEats.Data.js .
  2. Busque la función FriendlyEats.prototype.getAllRestaurants .
  3. Reemplace toda la función con el siguiente código.

FriendlyEats.Data.js

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

  this.getDocumentsInQuery(query, renderer);
};

En el código anterior, construimos una consulta que recuperará hasta 50 restaurantes de la colección de nivel superior llamada restaurants , que están ordenados por calificación promedio (actualmente todo cero). Después de declarar esta consulta, la pasamos al método getDocumentsInQuery() que es responsable de cargar y representar los datos.

Haremos esto agregando un oyente de instantáneas.

  1. Vuelva al archivo scripts/FriendlyEats.Data.js .
  2. Busque la función FriendlyEats.prototype.getDocumentsInQuery .
  3. Reemplace toda la función con el siguiente código.

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

En el código anterior, query.onSnapshot activará su devolución de llamada cada vez que haya un cambio en el resultado de la consulta.

  • La primera vez, la devolución de llamada se activa con el conjunto completo de resultados de la consulta, es decir, toda la colección de restaurants de Cloud Firestore. Luego pasa todos los documentos individuales a la función renderer.display .
  • Cuando se elimina un documento, change.type es igual a removed . Entonces, en este caso, llamaremos a una función que elimina el restaurante de la interfaz de usuario.

Ahora que implementamos ambos métodos, actualice la aplicación y verifique que los restaurantes que vimos anteriormente en Firebase Console ahora estén visibles en la aplicación. Si completó esta sección con éxito, ¡entonces su aplicación ahora está leyendo y escribiendo datos con Cloud Firestore!

A medida que cambie su lista de restaurantes, este oyente seguirá actualizándose automáticamente. Intente ir a la consola de Firebase y eliminar manualmente un restaurante o cambiar su nombre. ¡Verá los cambios que aparecen en su sitio de inmediato!

img5.png

8. Obtener () datos

Hasta ahora, hemos mostrado cómo usar onSnapshot para recuperar actualizaciones en tiempo real; sin embargo, eso no es siempre lo que queremos. A veces tiene más sentido buscar los datos solo una vez.

Querremos implementar un método que se activa cuando un usuario hace clic en un restaurante específico en su aplicación.

  1. Vuelva a su archivo scripts/FriendlyEats.Data.js .
  2. Encuentra la función FriendlyEats.prototype.getRestaurant .
  3. Reemplace toda la función con el siguiente código.

FriendlyEats.Data.js

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

Una vez que haya implementado este método, podrá ver las páginas de cada restaurante. Simplemente haga clic en un restaurante de la lista y debería ver la página de detalles del restaurante:

img1.png

Por ahora, no puede agregar calificaciones, ya que aún debemos implementar la adición de calificaciones más adelante en el laboratorio de código.

9. Ordenar y filtrar datos

Actualmente, nuestra aplicación muestra una lista de restaurantes, pero no hay forma de que el usuario filtre según sus necesidades. En esta sección, utilizará las consultas avanzadas de Cloud Firestore para habilitar el filtrado.

Aquí hay un ejemplo de una consulta simple para obtener todos los restaurantes Dim Sum :

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

Como su nombre lo indica, el método where() hará que nuestra consulta descargue solo los miembros de la colección cuyos campos cumplan con las restricciones que establezcamos. En este caso, solo descargará restaurantes donde category sea Dim Sum .

En nuestra aplicación, el usuario puede encadenar varios filtros para crear consultas específicas, como "Pizza en San Francisco" o "Mariscos en Los Ángeles ordenados por popularidad".

Crearemos un método que genere una consulta que filtrará nuestros restaurantes en función de múltiples criterios seleccionados por nuestros usuarios.

  1. Vuelva a su archivo scripts/FriendlyEats.Data.js .
  2. Busque la función FriendlyEats.prototype.getFilteredRestaurants .
  3. Reemplace toda la función con el siguiente código.

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

El código anterior agrega múltiples filtros where y una sola cláusula orderBy para crear una consulta compuesta basada en la entrada del usuario. Nuestra consulta ahora solo devolverá restaurantes que coincidan con los requisitos del usuario.

Actualice su aplicación FriendlyEats en su navegador, luego verifique que puede filtrar por precio, ciudad y categoría. Durante la prueba, verá errores en la Consola de JavaScript de su navegador que se ven así:

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

Estos errores se deben a que Cloud Firestore requiere índices para la mayoría de las consultas compuestas. Requerir índices en las consultas mantiene a Cloud Firestore rápido a escala.

Al abrir el vínculo del mensaje de error, se abrirá automáticamente la IU de creación de índices en Firebase console con los parámetros correctos completados. En la siguiente sección, escribiremos e implementaremos los índices necesarios para esta aplicación.

10. Implementar índices

Si no queremos explorar cada ruta en nuestra aplicación y seguir cada uno de los enlaces de creación de índices, podemos implementar fácilmente muchos índices a la vez usando Firebase CLI.

  1. En el directorio local descargado de su aplicación, encontrará un archivo firestore.indexes.json .

Este archivo describe todos los índices necesarios para todas las posibles combinaciones de filtros.

firestore.indexes.json

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

   ...

 ]
}
  1. Implemente estos índices con el siguiente comando:
firebase deploy --only firestore:indexes

Después de unos minutos, sus índices estarán activos y los mensajes de error desaparecerán.

11. Escribir datos en una transacción

En esta sección, agregaremos la posibilidad de que los usuarios envíen reseñas a los restaurantes. Hasta ahora, todas nuestras escrituras han sido atómicas y relativamente simples. Si alguno de ellos tiene un error, es probable que solo solicitemos al usuario que vuelva a intentarlo o nuestra aplicación volverá a intentar la escritura automáticamente.

Nuestra aplicación tendrá muchos usuarios que deseen agregar una calificación para un restaurante, por lo que necesitaremos coordinar varias lecturas y escrituras. Primero se debe enviar la reseña en sí, luego se debe actualizar el count de calificaciones del restaurante y la calificación average rating . Si uno de estos falla pero el otro no, nos quedamos en un estado inconsistente donde los datos en una parte de nuestra base de datos no coinciden con los datos en otra.

Afortunadamente, Cloud Firestore brinda una funcionalidad de transacción que nos permite realizar múltiples lecturas y escrituras en una sola operación atómica, lo que garantiza que nuestros datos permanezcan consistentes.

  1. Vuelva a su archivo scripts/FriendlyEats.Data.js .
  2. Encuentra la función FriendlyEats.prototype.addRating .
  3. Reemplace toda la función con el siguiente código.

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

En el bloque anterior, activamos una transacción para actualizar los valores numéricos de avgRating y numRatings en el documento del restaurante. Al mismo tiempo, agregamos la nueva rating a la subcolección de ratings .

12. Proteja sus datos

Al comienzo de este laboratorio de código, configuramos las reglas de seguridad de nuestra aplicación para abrir completamente la base de datos a cualquier lectura o escritura. En una aplicación real, nos gustaría establecer reglas mucho más detalladas para evitar el acceso o la modificación de datos no deseados.

  1. En la sección Crear de la consola Firebase, haga clic en Base de datos de Firestore .
  2. Haga clic en la pestaña Reglas en la sección Cloud Firestore (o haga clic aquí para ir directamente allí).
  3. Reemplace los valores predeterminados con las siguientes reglas, luego haga clic en Publicar .

firestore.reglas

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

Estas reglas restringen el acceso para garantizar que los clientes solo realicen cambios seguros. Por ejemplo:

  • Las actualizaciones de un documento de restaurante solo pueden cambiar las calificaciones, no el nombre ni ningún otro dato inmutable.
  • Las calificaciones solo se pueden crear si la ID de usuario coincide con el usuario que inició sesión, lo que evita la suplantación de identidad.

Como alternativa a usar Firebase console, puede usar Firebase CLI para implementar reglas en su proyecto de Firebase. El archivo firestore.rules en su directorio de trabajo ya contiene las reglas anteriores. Para implementar estas reglas desde su sistema de archivos local (en lugar de usar la consola de Firebase), debe ejecutar el siguiente comando:

firebase deploy --only firestore:rules

13. Conclusión

En este laboratorio de código, aprendió cómo realizar lecturas y escrituras básicas y avanzadas con Cloud Firestore, así como también cómo proteger el acceso a los datos con reglas de seguridad. Puede encontrar la solución completa en el repositorio quickstarts-js .

Para obtener más información sobre Cloud Firestore, visite los siguientes recursos: