Cloud Firestore es una base de datos NoSQL sin servidor y muy rápida, perfecta para alimentar aplicaciones web y móviles de cualquier tamaño. Obtenga la guía completa para el aprendizaje de Firestore, creada para mostrar cómo utilizar Firestore, como motor de sus propios proyectos increíbles de principio a fin.

Tabla de contenidos

Cómo empezar a usar Firestore

  • ¿Qué es Firestore? ¿Por qué debería utilizarlo?
  • Configuración de Firestore en un proyecto JavaScript
  • Documentos y colecciones de Firestore
  • Gestionar nuestra base de datos con la consola de Firebase

Obtención de datos con Firestore

  • Obtención de datos de una colección con .get()
  • Suscripción a una colección con .onSnapshot()
  • Diferencia entre .get() y .onSnapshot()
  • Desuscripción de una colección
  • Obtención de documentos individuales

Modificación de datos con Firestore

  • Añadir un documento a una colección con .add()
  • Añadir un documento a una colección con .set()
  • Actualización de datos existentes
  • Eliminación de datos

Patrones esenciales

  • Trabajar con subcolecciones
  • Métodos útiles para los campos de Firestore
  • Consulta con .where()
  • Ordenar y limitar los datos

Nota: puedes descargar una versión en PDF de este tutorial para poder leerlo sin conexión.

¿Qué es Firestore? ¿Por qué debería utilizarlo?

Firestore es una base de datos muy flexible y fácil de usar para el desarrollo de móviles, web y servidores. Si estás familiarizado con la base de datos en tiempo real de Firebase, Firestore tiene muchas similitudes, pero con una API diferente (posiblemente más declarativa).

Estas son algunas de las características que aporta Firestore:

⚡️Obtenga fácilmente datos en tiempo real

Al igual que la base de datos en tiempo real de Firebase, Firestore proporciona métodos útiles como .onSnapshot(), que hacen que sea muy fácil escuchar las actualizaciones de tus datos en tiempo real. Esto hace que Firestore sea una opción ideal para los proyectos que dan prioridad a la visualización y el uso de los datos más recientes (aplicaciones de chat, por ejemplo).

? Flexibilidad como base de datos NoSQL

Firestore es una opción muy flexible para backend, porque es una base de datos NoSQL. NoSQL significa que los datos no se almacenan en tablas y columnas como lo haría una base de datos SQL estándar. Está estructurada como un almacén de valores clave, como si fuera un gran objeto de JavaScript.

En otras palabras, no hay ningún esquema, ni necesidad de describir qué datos almacenará nuestra base de datos. Mientras proporcionemos claves y valores válidos, Firestore los almacenará.

↕️ Escalable sin esfuerzo

Una de las grandes ventajas de elegir Firestore, para su base de datos es la potente infraestructura sobre la que se asienta y que le permite escalar su aplicación con gran facilidad. Tanto vertical como horizontalmente. No importa si tienes cientos o millones de usuarios. Los servidores de Google serán capaces de soportar cualquier carga que les impongas.

En resumen, Firestore es una gran opción para aplicaciones tanto pequeñas como grandes. Para aplicaciones pequeñas es potente, porque podemos hacer mucho sin mucha configuración y crear proyectos rápidamente con ellos. Firestore es muy adecuado, para proyectos grandes debido a su escalabilidad.

Configuración de Firestore en un proyecto JavaScript

Vamos a utilizar el SDK de Firestore para JavaScript. A lo largo de esta hoja de trucos, cubriremos cómo utilizar Firestore dentro del contexto de un proyecto de JavaScript. A pesar de esto, los conceptos que cubriremos aquí son fácilmente transferibles a cualquiera de las librerías cliente de Firestore disponibles.

Para empezar con Firestore, nos dirigiremos a la consola de Firebase. Puedes visitarla yendo a firebase.google.com. Tendrás que tener una cuenta de Google para iniciar sesión.

Firebase

Una vez iniciada la sesión, crearemos un nuevo proyecto y le daremos un nombre.

Crea un proyecto

Una vez creado nuestro proyecto, lo seleccionaremos. Después, en el tablero de nuestro proyecto, seleccionaremos el botón de código.

Registra la aplicación

Esto nos dará el código que necesitamos para integrar Firestore con nuestro proyecto JavaScript.

código de SDK

Normalmente, si estás configurando esto en cualquier tipo de aplicación JavaScript, querrás ponerlo en un archivo dedicado llamado firebase.js. Si estás usando cualquier biblioteca de JavaScript que tiene un archivo package.json, querrás instalar la dependencia de Firebase con npm o yarn.

// Con npm
npm i firebase

// Con yarn
yarn add firebase
Dependencia

Firestore puede utilizarse tanto en el cliente como en el servidor. Si estás usando Firestore con Node, tendrás que usar la sintaxis de CommonJS con require. De lo contrario, si estás usando JavaScript en el cliente, importarás firebase usando ES Modules.

// Con sintaxis Commonjs (Si, estas usando Node)
const firebase = require("firebase/app");
require("firebase/firestore");

// Con ES Modules (Si, estas usando del lado del cliente, parecido a React)
import firebase from 'firebase/app';
import 'firebase/firestore';

var firebaseConfig = {
  apiKey: "AIzaSyDpLmM79mUqbMDBexFtOQOkSl0glxCW_ds",
  authDomain: "lfasdfkjkjlkjl.firebaseapp.com",
  databaseURL: "https://lfasdlkjkjlkjl.firebaseio.com",
  projectId: "lfasdlkjkjlkjl",
  storageBucket: "lfasdlkjkjlkjl.appspot.com",
  messagingSenderId: "616270824980",
  appId: "1:616270824990:web:40c8b177c6b9729cb5110f",
};
// Inicializar Firebase
firebase.initializeApp(firebaseConfig);
Sintaxis

Colecciones y documentos de Firestore

Hay dos términos clave que son esenciales para entender cómo trabajar con Firestore: documentos y colecciones.

Los documentos son piezas individuales de datos en nuestra base de datos. Se puede pensar en los documentos, como si fueran simples objetos de JavaScript. Constan de pares clave-valor, a los que nos referimos como campos. Los valores de estos campos pueden ser strings, números, booleanos, objetos, matrices e incluso datos binarios.

documento -> { key: value } 
Documento en Firebase

Los conjuntos de estos documentos se conocen como colecciones. Las colecciones son muy parecidas a los arreglos de objetos. Dentro de una colección, cada documento está vinculado a un identificador determinado (id).

colleción -> [{ id: doc }, { id: doc }]
Collección en Firebase

Gestionar nuestra base de datos con la consola de Firestore

Antes de empezar a trabajar con nuestra base de datos tenemos que crearla.

Dentro de nuestra consola de Firebase, vaya a la pestaña "Base de datos(Database)" y cree su base de datos Firestore.

Base de datos Firestore

Una vez hecho esto, comenzaremos en modo de prueba y habilitaremos todas las lecturas y escrituras en nuestra base de datos. En otras palabras, tendremos acceso abierto para obtener y cambiar datos en nuestra base de datos. Si añadiéramos la autentificación de Firebase, podríamos restringir el acceso sólo a los usuarios autentificados.

Después de eso, seremos llevados a nuestra base de datos en sí, donde podemos empezar a crear colecciones y documentos. La raíz de nuestra base de datos será una serie de colecciones, así que vamos a hacer nuestra primera colección.

Podemos seleccionar 'Iniciar colección' y darle un id. Cada colección va a tener un id o un nombre. Para nuestro proyecto, vamos a llevar un registro de los libros favoritos de nuestros usuarios. Le daremos a nuestra primera colección el id 'libros'.

Database

A continuación, añadiremos nuestro primer documento con nuestra recién creada colección "libros".

Cada documento va a tener también un id, que lo vincula a la colección en la que existe.

En la mayoría de los casos, vamos a utilizar una opción para darle un ID generado automáticamente. Así que podemos pulsar el botón 'auto id' para hacerlo, después de lo cual tenemos que proporcionar un campo, darle un tipo, así como un valor.

Para nuestro primer libro, haremos un campo 'title' de tipo 'string', con el valor 'The Great Gatsby', y pulsaremos guardar.

Después de eso, deberíamos ver nuestro primer elemento en nuestra base de datos.

Collection

Obtener datos de una colección con .get()

Para acceder a Firestore utilizar todos los métodos que proporciona, utilizamos firebase.firestore(). Este método debe ser ejecutado cada vez que queramos interactuar con nuestra base de datos Firestore.

Yo recomendaría crear una variable dedicada a almacenar una única referencia a Firestore. Hacerlo ayuda a reducir la cantidad de código que se escribe en toda la aplicación.

const db = firebase.firestore();
firestore
En esta hoja de trucos, sin embargo, me voy a ceñir a usar el método firestore cada vez para ser lo más claro posible.

Para referenciar una colección, utilizamos el método .collection() y proporcionamos el id de la colección como argumento. Para obtener una referencia a la colección de libros que hemos creado, basta con pasar la cadena 'books'.

const booksRef = firebase.firestore().collection('libros');
Obtener colecciones

Para obtener todos los datos del documento de una colección, podemos encadenar el método .get().

.get() devuelve una promesa, lo que significa que podemos resolverla usando un callback .then() o podemos usar la sintaxis async-await, si estamos ejecutando nuestro código dentro de una función async.

Una vez que nuestras promesas se resuelven de una manera u otra, obtenemos lo que se conoce como una instantánea.

Para una consulta de colección esa instantánea va a consistir en un número de documentos individuales. Podemos acceder a ellos diciendo snapshot.docs.

De cada documento, podemos obtener el id como una propiedad independiente, y el resto de los datos utilizando el método .data().

Este es el aspecto de nuestra consulta completa:

const booksRef = firebase
  .firestore()
  .collection("libros");

booksRef
  .get()
  .then((results) => {
    const data = results.docs.map((doc) => ({
      id: doc.id,
      ...doc.data(),
    }));
    console.log("Toda data en la colección 'libros' ", data); 
    // [ { id: 'glMeZvPpTN1Ah31sKcnj', titulo: 'El gran Gatsby' } ]
  });
Obtener referencias

Suscripción a una colección con .onSnapshot()

El método .get() simplemente devuelve todos los datos dentro de nuestra colección.

Para aprovechar algunas de las capacidades en tiempo real de Firestore podemos suscribirnos a una colección, lo que nos da el valor actual de los documentos de esa colección, siempre que se actualicen.

En lugar de utilizar el método .get(), que es para consultar una sola vez, utilizamos el método .onSnapshot().

firebase
  .firestore()
  .collection("libros")
  .onSnapshot((resultados) => {
    const datos = resultados.docs.map((doc) => ({
      id: doc.id,
      ...doc.data(),
    }));
    console.log("Todos los datos de la colección 'libros'", datos);
  });
Método onSnapshot

En el código anterior, estamos utilizando lo que se conoce como encadenamiento de métodos, en lugar de crear una variable separada para referenciar la colección.

Lo potente de usar firestore, es que podemos encadenar un montón de métodos uno detrás de otro, haciendo un código más declarativo y legible.

Dentro del callback de onSnapshot, obtenemos acceso directo a la instantánea(snapshot) de nuestra colección, tanto como ahora y cuando se actualice en el futuro. Intenta actualizar manualmente nuestro único documento y verás que .onSnapshot() está a la escucha de cualquier cambio en esta colección.

Diferencia entre .get() y .onSnapshot()

La diferencia entre los métodos get y snapshot es que get devuelve una promesa, que necesita ser resuelta, y sólo entonces obtenemos los datos de la instantánea.

.onSnapshot, sin embargo, utiliza una función de devolución de llamada sincrónica, que nos da acceso directo a la instantánea.

Esto es importante para tener en cuenta, cuando se trata diferentes métodos - tenemos que saber cuáles de ellos devuelven una promesa y cuáles son sincrónicos.

Desinscripción de una colección con unsubscribe()

Obsérve además que .onSnapshot() devuelve una función que podemos utilizar para desuscribirnos y dejar el proceso en una colección determinada.

Esto es importante en los casos en los que el usuario, por ejemplo, se va de una determinada página en la que estamos mostrando los datos de una colección. Aquí hay un ejemplo, usando la librería React donde estamos llamando a cancelar la suscripción dentro del hook useEffect.

Cuando lo hagamos, nos aseguraremos que nuestro componente sea desmontado (ya no se muestre en el contexto de nuestra aplicación) ya no estaremos escuchando los datos de la colección que estamos utilizando en este componente.

function App() {
  const [libros, setLibros] = React.useState([]);

  React.useEffect(() => {
	const unsubscribe = firebase
      .firestore()
      .collection("libros")
      .onSnapshot((resultados) => {
        const datos = resultados.docs.map((doc) => ({
          id: doc.id,
          ...doc.data(),
        }));
		setLibros(datos);
      });
  }, []);
  
  return libros.map(libro => <BookList key={libro.id} libro={libro} />)
}
Desuscribirse

Obtener documentos individuales con .doc()

Cuando se trata de obtener un documento dentro de una colección, el proceso es el mismo para obtener una colección completa: primero tenemos que crear una referencia a ese documento y luego utilizar el método get para tomarlo.

Sin embargo, después utilizamos el método .doc() encadenado al método de la colección. Para crear una referencia, necesitamos tomar este id de la base de datos si fue autogenerado. Después de eso, podemos encadenar a .get() y resolver la promesa.

const libroRef = firebase
  .firestore()
  .collection("libros")
  .doc("glMeZvPpTN1Ah31sKcnj");

libroRef.get().then((doc) => {
  if (!doc.exists) return;
  console.log("Document data:", doc.data());
  // Document data: { titulo: 'El gran Gatsby' }
});
Firebase get

Observe el condicional if (!doc.exists) return; en el código anterior.

Una vez recuperado el documento, es imprescindible comprobar si existe.

Si no lo hacemos, habrá un error al obtener los datos de nuestro documento. La forma de comprobar y ver si nuestro documento existe es diciendo, si doc.exists, que devuelve un valor verdadero o falso.

Si esta expresión devuelve false, queremos volver de la función o tal vez lanzar un error. Si doc.exists es verdadero, podemos obtener los datos de doc.data.

Añadir un documento a una colección con .add()

A continuación, pasemos a cambiar los datos. La forma más sencilla de añadir un nuevo documento a una colección es con el método .add().

Todo lo que tiene que hacer es seleccionar una referencia de colección (con .collection()) y encadenar en .add().

Volviendo a nuestra definición de los documentos como objetos de JavaScript, tenemos que pasar un objeto al método .add() y especificar todos los campos que queremos que estén en el documento.

Digamos que queremos añadir otro libro, "De ratones y hombres":

firebase
  .firestore()
  .collection("libros")
  .add({
    title: "De ratones y hombres",
  })
  .then((ref) => {
    console.log("Documento añadido con ID: ", ref.id);
    // Documento añadido con ID:  ZzhIgLqELaoE3eSsOazu
  });
Firebase add()

El método .add devuelve una promesa y de esta promesa resuelta, obtenemos de vuelta una referencia al documento creado, que nos da información del id creado.

El método .add() nos genera automáticamente un id. Ten en cuenta que no podemos utilizar esta referencia directamente para obtener datos. Sin embargo, podemos pasar la refeferencia al método doc para crear otra consulta.

Añadir un documento a una colección con .set()

Otra forma de añadir un documento a una colección es con el método .set().

La diferencia entre set y add, radica en la necesidad de especificar nuestro propio id al añadir los datos.

Esto requiere encadenar en el método .doc() con el id, que se quiere utilizar. Además, observa cómo cuando la promesa se resuelve desde .set(), no obtenemos una referencia al documento creado:

firebase
  .firestore()
  .collection("libros")
  .doc("otro libro")
  .set({
    title: "Guerra y paz",
  })
  .then(() => {
    console.log("Documento creado");
  });
Firebase set()

Además, cuando usamos .set() con un documento existente, por defecto se sobrescribirá ese documento.

Si queremos fusionar, un documento antiguo con un documento nuevo en lugar de sobrescribirlo, necesitamos pasar un argumento adicional a .set() y proporcionar la propiedad merge set a true.

// use .set() para fusionar los datos con documentos existentes y no sobreescribirlos

const libroRef = firebase
  .firestore()
  .collection("libros")
  .doc("otro libro");

libroRef
  .set({
    author: "Lev Nikolaevich Tolstoy"
  }, { merge: true })
  .then(() => {
    console.log("Documento fusionado");
    
    libroRef
      .get()
      .then(doc => {
      console.log("Documento fusionado: ", doc.data());
      // Documento fusionado:  { titulo: 'Guerra  y paz', author: 'Lev Nikolaevich Tolstoy' }
    });
  });
Firebase set 

Actualización de datos existentes con .update()

Cuando se trata de actualizar datos, utilizamos el método update, que al igual que .add() y .set() devuelve una promesa.

Lo útil de usar .update() es que, a diferencia de .set(), no sobrescribirá todo el documento. También como .set(), necesitamos referenciar un documento individual.

Cuando se utiliza .update(), es importante utilizar algún tipo de manejo de errores, como el callback .catch() en el caso de que el documento no exista.

const libroRef = firebase.firestore().collection("libros").doc("otro libro");

libroRef
  .update({
    year: 1869,
  })
  .then(() => {
    console.log("Documento actualizado"); // Documento actualizado
  })
  .catch((error) => {
    console.error("Error de actualización de doumento", error);
  });	
Firebase update

Eliminación de datos con .delete()

Podemos eliminar una colección de documentos dada referenciándola por su id y ejecutando el método .delete(), así de simple. También devuelve una promesa.

Este es un ejemplo básico de eliminación de un libro con el id "otro libro":

firebase
  .firestore()
  .collection("libros")
  .doc("otro libro")
  .delete()
  .then(() => console.log("Documento borrado")) // Documento borrado
  .catch((error) => console.error("Error eliminando documento", error));
Firebase update()
Tenga en cuenta que la documentación oficial de Firestore no recomienda eliminar colecciones enteras, sólo documentos individuales.

Trabajar con subcolecciones(Colecciones)

Supongamos que hemos dado un paso en falso al crear nuestra aplicación y en lugar de limitarnos a añadir libros queremos, también conectarlos a los usuarios que los han hecho. T

La forma en que queremos reestructurar los datos, es creando una colección llamada 'usuarios' en la raíz de nuestra base de datos y que 'libros' sea una subcolección de 'usuarios'. Esto permitirá a los usuarios tener sus propias colecciones de libros. ¿Cómo lo configuramos?

Las referencias a la subcolección "libros" deberían tener el siguiente aspecto:

const usuarioLibrosRef = firebase
  .firestore()
  .collection('usuarios')
  .doc('user-id')
  .collection('libros');
Subcolecciones de colecciones

Observe además que podemos escribir todo esto dentro de una sola llamada a .collection() utilizando barras inclinadas.

El código anterior es equivalente al siguiente, donde la referencia de la colección debe tener un número impar de segmentos. Si no es así, Firestore arrojará un error.

const usuarioLibrosRef = firebase
  .firestore()
  .collection('usuarios/user-id/libros');
Colleción de usuarios

Para crear la subcolección propia, con un documento (otra novela de Steinbeck, "Al este del Edén") ejecute lo siguiente:

firebase.firestore().collection("usuarios/user-1/libros").add({
  titulo: "Al este del Eden",
});
Firestore

Entonces, obtener esa subcolección recién creada tendría el siguiente aspecto basado en el ID del usuario.

firebase
  .firestore()
  .collection("usuarios/user-1/libros")
  .get()
  .then((resultados) => {
    const datos = resultados.docs.map((doc) => ({
      id: doc.id,
      ...doc.data(),
    }));
    console.log(datos); 
    // [ { id: 'UO07aqpw13xvlMAfAvTF', titulo: 'Al este del Eden' } ]
  });
SuColecciones

Métodos útiles para los campos de Firestore

Hay algunas herramientas útiles que podemos tomar de Firestore, las cuales permiten trabajar con nuestros valores de campo un poco más fácil.

Por ejemplo, podemos generar una marca de tiempo(timestamp) para cada vez que se crea o actualiza un determinado documento, con el siguiente helper de la propiedad FieldValue.

Por supuesto, podemos crear nuestros propios valores de fecha utilizando JavaScript, pero el uso de una marca de tiempo(timestamp) del servidor, nos permite saber exactamente cuándo se modifican o crean los datos desde el propio Firestore.F

firebase
  .firestore()
  .collection("usuarios")
  .doc("user-2")
  .set({
    created: firebase.firestore.FieldValue.serverTimestamp(),
  })
  .then(() => {
    console.log("Usuario añadido"); // Usuario añadido
  });
Firebase usuarios

Adicionalmente, digamos que tenemos un campo un documento que lleva la cuenta de un cierto número, digamos el número de libros de un usuario ha creado. Cada vez que un usuario crea un nuevo libro queremos incrementar su cantidad en uno.

Una forma fácil de hacer esto, en lugar de tener que hacer primero una petición .get(), es utilizar otro ayudante de valor de campo llamado .increment():

const userRef = firebase.firestore().collection("usuarios").doc("user-2");

usuarioRef
  .set({
    count: firebase.firestore.FieldValue.increment(1),
  })
  .then(() => {
    console.log("Usuario actualizado");

    usuarioRef.get().then((doc) => {
      console.log("Datos de usuario actualizado: ", doc.data());
    });
  });
 

Consulta con .where()

¿Si queremos obtener datos de nuestras colecciones en función de determinadas condiciones? Por ejemplo, digamos que queremos obtener todos los usuarios que han enviado uno o más libros.

Podemos escribir una consulta de este tipo con la ayuda del método .where(). Primero referenciamos una colección y luego encadenamos en .where().

El método where toma tres argumentos: en primer lugar, el campo en el que estamos buscando una operación, un operador y el valor sobre el que queremos filtrar nuestra colección.

Podemos utilizar cualquiera de los siguientes operadores y los campos que utilicemos pueden ser valores primitivos así como arrays.

<, <=, ==, >, >=, array-contains, in, or array-contains-any

Para obtener todos los usuarios que han enviado más de un libro, podemos utilizar la siguiente consulta.

Después de .where() necesitamos encadenar en .get(). Al resolver nuestra promesa obtenemos lo que se conoce como querySnapshot.

Al igual que para obtener una colección, podemos iterar sobre el querySnapshot con .map() para obtener el id y los datos(campos) de cada documento:

firebase
  .firestore()
  .collection("usuarios")
  .where("count", ">=", 1)
  .get()
  .then((resConsulta) => {
    const datos = resConsulta.docs.map((doc) => ({
      id: doc.id,
      ...doc.data(),
    }));
    console.log("Usuarios con > 1 libro: ", datos);
    // Usuarios con > 1 libro:  [ { id: 'user-1', count: 1 } ]
  });
Firebase where
Tenga en cuenta que puede encadenar en múltiples métodos .where () para crear consultas compuestas.

Limitación y ordenación de las consultas

Otro método para consultar eficazmente nuestras colecciones es limitarlas. Digamos que queremos limitar una consulta determinada a una cierta cantidad de documentos.

Si, sólo queremos devolver unos pocos elementos de nuestra consulta, sólo tenemos que añadir el método .limit(), después de una referencia determinada.

Si, quisiéramos hacer eso a través de nuestra consulta, para obtener los usuarios que han enviado al menos un libro, sería como lo siguiente:

const usuariosRef = firebase
  .firestore()
  .collection("usuarios")
  .where("count", ">=", 1);

  usuariosRef.limit(3)
Firebase limit()

Otra potente función es la de ordenar nuestros datos consultados, según los campos del documento utilizando .orderBy().

Si, queremos ordenar nuestros usuarios creados por la fecha en que se hicieron por primera vez, podemos utilizar el método orderBy, con el campo 'creado' como primer argumento. Para el segundo argumento, especificamos si debe ser en orden ascendente o descendente.

Para obtener todos los usuarios ordenados por fecha de creación del más reciente al más antiguo, podemos ejecutar la siguiente consulta:

const usuariosRef = firebase
  .firestore()
  .collection("usuarios")
  .where("count", ">=", 1);

  usuariosRef.orderBy("created", "desc").limit(3);
Firebse orderBy()

Podemos encadenar .orderBy() con .limit(). Para que esto funcione correctamente, .limit() debe llamarse en último lugar y no antes de .orderBy().

¿Quieres tu propia copia?

Si quieres tener esta guía para futuras consultas, descarga una hoja de trucos de todo este tutorial aquí.

Haga clic para obtener la hoja de trucos


Traducido del artículo de Reed Barger - The JavaScript + Firestore Tutorial for 2020: Learn by Example