Cómo usar Firebase Firestore como su base de datos

Un tutorial para configurar Firestore como una base de datos, ¡una solución muy conveniente para sus problemas de almacenamiento!

Tuve la necesidad de crear un almacenamiento para algunos datos para miClub de membresía, el lugar donde enseño programación.

Quería que mis usuarios pudieran decir manualmente "Completé este curso", haciendo clic en un botón.

Básicamente, quería almacenar un objeto particular para cada usuario.

Configurando Firebase

Decidí usarFirebasepara esto, y en particular elBase de datos de Firestoreellos proveen.

El nivel gratuito es generoso, con hasta 1 GB de datos almacenados y 10 GB de transferencia de red por mes. ¡Mucho excediendo mis estimaciones para lo que necesito!

Abra el sitio web de Firebase enhttps://firebase.google.com/

Firebase es un producto de Google, por lo que una vez que haya iniciado sesión en Google, esencialmente también lo estará en Firebase.

Creé un nuevo proyecto de Firebase haciendo clic en "Crear un proyecto"

Le di un nombre:

Y eso fue todo:

Hice clic en el ícono "Web" al lado de iOS y Android, e ingresé el nombre de la aplicación:

Y Firebase inmediatamente me dio las claves de acceso que necesitaba, junto con un código de muestra:

Inmediatamente después de eso, Firebase me pidió que agregara algunas reglas de seguridad para la base de datos.

Puede elegir 2 cosas por defecto: abierto para todos o cerrado para todos. Comencé a abrirme a todos, algo que ellos llamanModo de prueba.

¡Eso es! Estaba listo para comenzar, creando una colección.

¿Qué es una colección? En la terminología de Firestore, podemos crear muchas colecciones diferentes y asignar documentos a cada colección.

Luego, un documento puede contener campos y otras colecciones.

No es muy diferente a otras bases de datos NoSQL, comoMongoDB.

Recomiendo encarecidamente mirarla lista de reproducción de YouTube sobre el tema, está muy bien hecho.

Así que agregué mi colección, a la que llaméusers.

Quería identificar a cada usuario usando una cadena especial, a la que llamoid.

El código de la interfaz

Ahora llegamos a la parte de JavaScript.

En el pie de página, incluí esos 2 archivos, proporcionados por Firebase:

<script src="https://www.gstatic.com/
firebasejs/7.2.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/
firebasejs/7.2.1/firebase-firestore.js"></script>

luego agregué unDetector de eventos DOMContentLoaded, para asegurarme de que ejecuté el código cuando el DOM estuviera listo:

<script>
document.addEventListener('DOMContentLoaded', event => {

}) </script>

Allí, agregué la configuración de Firebase:

const firebaseConfig = {
  apiKey: "MY-API-KEY",
  authDomain: "MY-AUTH-DOMAIN",
  projectId: "MY-PROJECT-ID"
}

Le pasé este objeto afirebase.initializeApp(), y luego llaméfirebase.firestore()para obtener la referencia al objeto de la base de datos:

firebase.initializeApp(firebaseConfig)
const db = firebase.firestore()

Ahora, creé un script para completar las ID de usuario de una lista que tenía en mi backend, usando un ciclo simple:

const list = [/*...my list...*/]

list.forEach(item => { db.collection(‘users’).doc(item).set({}) })

..y lo ejecuté una vez, para poblar la base de datos. Yo básicamente programáticamentecreó un documento para cada usuario.

Esto es muy importante, porque una vez que creé un documento, significaba que podía restringir los permisos para actualizar solo esos documentos y no permitir agregar nuevos o eliminarlos (algo que haremos más adelante).

Ok, ahora tenía una lógica compleja para identificar el ID de usuario y el ID del curso, que no entraré porque no está relacionado con nuestra tarea aquí.

Una vez que reuní eso, pude obtener una referencia al objeto:

const id = /* the user ID */
const course = /* the course ID */
const docRef = db.doc(`membership/${id}`)

¡Genial! Ahora podría obtener la referencia del documento de Firebase:

docRef.get().then(function(doc) {
  if (doc.exists) {
    const data = doc.data()
    document.querySelector('button')
      .addEventListener('click', () => {
      data[course] = true
      docRef.update(data)
    })
  } else {
    //user does not exist..
  }
})

Mi lógica en realidad era mucho más complicada porque tengo otras partes móviles, ¡pero entiendes la idea!

Inicializo los datos del documento llamandodoc.data()y cuando se hace clic en el botón, que supongo que es el botón que dice "Completé el curso", asociamos eltruevalor booleano para el identificador del club.

Más adelante, en cargas posteriores de la página de la lista de cursos, puedo inicializar la página y asignar una clase si el curso se completó, así:

for (const [key, value] of Object.entries(data[course])) {
  const element = document.querySelector('.course-' + course)
  if (element) {
    element.classList.add('completed')
  }
}

El problema de los permisos

Inicié Firebase en modo de prueba, ¿recuerdas? Lo que hace que la base de datos esté abierta para todos: todos los que tienen las claves de acceso, que son públicas y se publican en el código enviado a la interfaz.

Así que tenía que hacer una cosa: decidir el nivel de permiso permitido.

Y me encontré con un problema bastante importante.

Usando la consola de Firebase, debajo deNormas, podemos recortar el permiso. Inicialmente, esta era la regla predeterminada:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write;
    }
  }
}

Cambié las reglas aread, update, por lo que solo se puede actualizar un documento, no crear otros nuevos:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, update;
    }
  }
}

Pero no pude evitar que las personas usaran la API de Firebase, ahora disponible gratuitamente en el navegador, para jugar y enumerar todos los demás documentos de la colección, obteniendo acceso a los archivos de otras personas.

Si bien esto no manejó ningún dato sensible, no fue posible enviar este código.

Mover el código del frontend al backend a través de una API personalizada

El problema del permiso era un obstáculo en la carretera.

Pensé en eliminar todo el código que tenía, pero finalmente descubrí que podía ocultar todo el acceso a la API del navegador por completo y usar un servicio Node.js para ejecutar la API de Firebase.

Este también es un método común para ocultar claves privadas / secretas requeridas por los servicios: ocúltelas detrás de un servidor que controle.

En lugar de llamar a Firebase desde el navegador, creé un conjunto de puntos finales en mi propio servidor, por ejemplo:

  • Publicar en/coursepara establecer un curso como completado
  • Publicar en/datapara obtener los datos asociados al usuario

y accedo a ellos usando elObtener API:

const options = {
    method: 'POST',
    headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
    },
    body: JSON.stringify({ id, course, lesson })
}
const url = BASE_URL + '/lesson'
fetch(url, options).catch(err => {
    console.error('Request failed', err)
})

Toda la lógica con los clics en los botones, etc., permanece en el código del lado del cliente, por supuesto, simplemente alejé la lógica de Firebase.

En el lado del servidor Node.js, instalé el oficialfirebasepaquete usandonpm install firebasey lo requirió:

const firebase = require('firebase')

Configuré unRápidoservidor para usarCORSe inicialicé Firebase:

const firebaseConfig = {
  apiKey: process.env.APIKEY,
  authDomain: process.env.AUTHDOMAIN,
  projectId: process.env.PROJECTID
}

firebase.initializeApp(firebaseConfig) const db = firebase.firestore()

Entonces, el código es exactamente como el que usé en la interfaz, excepto que ahora se activa en llamadas de punto final HTTP. Este es el código que devuelve un documento específico de nuestra colección.

const getData = async (id) =>  {
  const doc = await db.doc(`membership/${id}`).get()
  const data = doc.data()
  if (!data) {
    console.error('member does not exist')
    return
  }
  return data
}

app.post(’/data’, cors(), async (req, res) => { const id = req.body.id if (id) { res.json(await getData(id)) return } res.end() })

y aquí está la API para establecer un curso como completado:

const setCourseAsCompleted = async (id, course) => {
  const doc = await db.doc(`membership/${id}`).get()
  const data = doc.data()
  if (!data) {
    console.error('member does not exist')
    return
  }
  if (!data[course]) {
      data[course] = {}
  }
  data[course]['done'] = true
  db.doc(`membership/${id}`).update(data)
}

app.post(’/course’, cors(), (req, res) => { const id = req.body.id const course = req.body.course if (id && course) { setCourseAsCompleted(id, course) res.end(‘ok’) return } res.end() })

Eso es básicamente todo. Se requiere más código para manejar otra lógica, pero la esencia de Firebase es este que publiqué. Ahora también puedo agregar un usuario para mi servicio del lado del servidor y limitar todos los demás accesos a la API de Firebase y fortalecer la seguridad.


Más tutoriales de servicios: