Laboratorio de código web de Cloud Firestore

1. Información general

Objetivos

En este codelab, crearás 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
  • Escribe consultas complejas de Cloud Firestore

Lo que necesitarás

Antes de comenzar este codelab, asegúrese de haber instalado:

2. Crea y configura un proyecto de Firebase

Crear un proyecto de Firebase

  1. En Firebase console , haz clic en Agregar proyecto y luego nombra el proyecto de Firebase FriendlyEats .

Recuerde el ID del proyecto de su proyecto de Firebase.

  1. Haga clic en Crear proyecto .

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

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

Para este codelab 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 foco de este codelab, 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 silenciosamente sin que se le solicite.

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

  1. En Firebase console, busque la sección Construir en el panel de navegación izquierdo.
  2. Haga clic en Autenticación y 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 y luego haga clic en Guardar .

img7.png

Esto permitirá que la aplicación inicie sesión silenciosamente a 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 Generar de Firebase console, haz 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 codelab, 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/vanilla-js

Importar la aplicación de inicio

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

4. Instale 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 implementarla 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

Hemos configurado la plantilla de la aplicación web para extraer 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 acceda 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 y luego asigne un alias a su proyecto de Firebase.

Un alias es útil si tiene múltiples entornos (producción, puesta en escena, etc.). Sin embargo, para este codelab, usemos simplemente el alias 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 Firebase CLI:
firebase emulators:start --only hosting
  1. Su línea de comando debería mostrar la siguiente respuesta:
hosting: Local server: http://localhost:5000

Estamos usando el emulador de Firebase Hosting para ofrecer 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ías ver tu copia de FriendlyEats que se ha conectado a tu proyecto de Firebase.

La aplicación se conectó automáticamente a su proyecto de Firebase y silenciosamente inició sesión como usuario anónimo.

img2.png

6. Escribe datos en Cloud Firestore

En esta sección, escribiremos algunos datos en Cloud Firestore para poder 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 en 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 adelante, almacenaremos cada reseña en una subcolección llamada ratings debajo de cada restaurante.

img4.png

Agregar restaurantes a Firestore

El objeto modelo principal de nuestra aplicación es un restaurante. Escribamos un código que agregue un documento de restaurante a la colección 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 restaurants . Los datos del documento provienen de un objeto JavaScript simple. Para ello, primero obtenemos una referencia a restaurants de una colección de Cloud Firestore y luego add los datos.

¡Sumamos restaurantes!

  1. Vuelve a tu aplicación FriendlyEats en tu navegador y actualízala.
  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 aún necesitamos implementar la recuperación de datos (la siguiente sección del codelab).

Sin embargo, si navega a la pestaña Cloud Firestore en Firebase console, ¡ahora debería ver nuevos documentos en la colección 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 proporcionará la lista de restaurantes predeterminada y sin filtrar.

  1. Vuelva al archivo scripts/FriendlyEats.Data.js .
  2. Encuentra 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 denominada restaurants , que están ordenados por calificación promedio (actualmente todos 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 todo el conjunto de resultados de la consulta, es decir, toda la colección restaurants de Cloud Firestore. Luego pasa todos los documentos individuales a la función renderer.display .
  • Cuando se elimina un documento, change.type equivale a removed . Entonces, en este caso, llamaremos a una función que elimina el restaurante de la interfaz de usuario.

Ahora que hemos implementado 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 lee y escribe datos con Cloud Firestore.

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

img5.png

8. Obtener () datos

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

Querremos implementar un método que se active cuando un usuario haga 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 puedes agregar calificaciones ya que aún necesitamos implementar la adición de calificaciones más adelante en el codelab.

9. Ordenar y filtrar datos

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

A continuación se muestra un ejemplo de una consulta sencilla para buscar 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 establecimos. En este caso, solo descargará restaurantes cuya category sea Dim Sum .

En nuestra aplicación, el usuario puede encadenar múltiples 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 según múltiples criterios seleccionados por nuestros usuarios.

  1. Vuelva a su archivo scripts/FriendlyEats.Data.js .
  2. Encuentra 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 única 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 pueda filtrar por precio, ciudad y categoría. Mientras realiza la prueba, verá errores en la consola JavaScript de su navegador que se ven así:

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

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 y a escala.

Al abrir el enlace del mensaje de error, se abrirá automáticamente la interfaz de usuario 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 todas las rutas de 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 combinaciones posibles 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 sobre restaurantes. Hasta ahora, todas nuestras escrituras han sido atómicas y relativamente simples. Si alguno de ellos falla, probablemente simplemente le pediremos al usuario que lo vuelva a intentar o nuestra aplicación reintentará la escritura automáticamente.

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

Afortunadamente, Cloud Firestore proporciona una funcionalidad de transacciones que nos permite realizar múltiples lecturas y escrituras en una sola operación atómica, lo que garantiza que nuestros datos sigan siendo 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 ratings .

12. Asegure sus datos

Al comienzo de este codelab, 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 no deseada de los datos.

  1. En la sección Generar de Firebase console, haz 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 y luego haga clic en Publicar .

reglas.firestore

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 el ID de usuario coincide con el del usuario que inició sesión, lo que evita la suplantación de identidad.

Como alternativa a usar Firebase console, puedes usar Firebase CLI para implementar reglas en tu 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 Firebase), debe ejecutar el siguiente comando:

firebase deploy --only firestore:rules

13. Conclusión

En este codelab, aprendió a realizar lecturas y escrituras básicas y avanzadas con Cloud Firestore, además de 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:

14. [Opcional] Hacer cumplir con App Check

Firebase App Check brinda protección al ayudar a validar y prevenir el tráfico no deseado a su aplicación. En este paso, asegurará el acceso a sus servicios agregando App Check con reCAPTCHA Enterprise .

Primero, deberá habilitar App Check y reCaptcha.

Habilitando reCaptcha Enterprise

  1. En la consola de la nube, busque y seleccione reCaptcha Enterprise en Seguridad.
  2. Habilite el servicio cuando se le solicite y haga clic en Crear clave .
  3. Ingrese un nombre para mostrar cuando se le solicite y seleccione Sitio web como su tipo de plataforma.
  4. Agregue sus URL implementadas a la lista de dominios y asegúrese de que la opción "Usar desafío de casilla de verificación" no esté seleccionada.
  5. Haga clic en Crear clave y guarde la clave generada en algún lugar para su custodia. Lo necesitará más adelante en este paso.

Habilitar la verificación de aplicaciones

  1. En la consola de Firebase, ubique la sección Construir en el panel izquierdo.
  2. Haga clic en App Check , luego haga clic en el botón Comenzar (o redirija directamente a la consola ).
  3. Haga clic en Registrarse e ingrese su clave reCaptcha Enterprise cuando se le solicite, luego haga clic en Guardar .
  4. En la Vista de API, seleccione Almacenamiento y haga clic en Aplicar . Haga lo mismo con Cloud Firestore .

¡La verificación de aplicaciones ahora debería aplicarse! Actualiza tu aplicación e intenta crear/ver un restaurante. Debería recibir el mensaje de error:

Uncaught Error in snapshot listener: FirebaseError: [code=permission-denied]: Missing or insufficient permissions.

Esto significa que App Check bloquea las solicitudes no validadas de forma predeterminada. Ahora agreguemos validación a su aplicación.

Navegue hasta el archivo FriendlyEats.View.js , actualice la función initAppCheck y agregue su clave reCaptcha para inicializar App Check.

FriendlyEats.prototype.initAppCheck = function() {
    var appCheck = firebase.appCheck();
    appCheck.activate(
    new firebase.appCheck.ReCaptchaEnterpriseProvider(
      /* reCAPTCHA Enterprise site key */
    ),
    true // Set to true to allow auto-refresh.
  );
};

La instancia appCheck se inicializa con un ReCaptchaEnterpriseProvider con su clave, e isTokenAutoRefreshEnabled permite que los tokens se actualicen automáticamente en su aplicación.

Para habilitar las pruebas locales, busque la sección donde se inicializa la aplicación en el archivo FriendlyEats.js y agregue la siguiente línea a la función FriendlyEats.prototype.initAppCheck :

if(isLocalhost) {
  self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
}

Esto registrará un token de depuración en la consola de su aplicación web local similar a:

App Check debug token: 8DBDF614-649D-4D22-B0A3-6D489412838B. You will need to add it to your app's App Check settings in the Firebase console for it to work.

Ahora, vaya a la Vista de aplicaciones de App Check en Firebase console.

Haga clic en el menú adicional y seleccione Administrar tokens de depuración .

Luego, haga clic en Agregar token de depuración y pegue el token de depuración desde su consola cuando se le solicite.

¡Felicidades! App Check ahora debería estar funcionando en su aplicación.