Base de Datos en Tiempo Real con Firebase

8 minuto(s) de lectura

Toda la información oficial de Firebase la pueden encontrar aquí.

Introducción

Firebase nos proporciona una base de datos en tiempo real, permite trabajar de forma sin conexión y se puede utilizar con Android, iOS y web. Es una base de datos NoSQL y por lo mismo, almacena los datos en un archivo con formato JSON.

Utilizar la base de datos de Firebase nos da la ventaja de que está en la nube, así evitamos todo lo relacionado con el soporte y mantenimiento y además se puede ir expandiendo conforme aumente el uso.

Para tener acceso a la base de datos, debemos entrar en la consola de Firebase, en el menú Base de Datos. En esa pantalla tenemos acceso a nuestra base de datos, a la configuración de reglas, estadísticas de uso y copias de seguridad.

Base de datos en la consola.

En este Post nos enfocaremos en entender y aprender cómo crear las reglas, que son la base para la seguridad de nuestra base de datos. En el siguiente Post, crearemos una base de datos e implementaremos el código necesario para utilizar la base desde una aplicación Android.

Reglas

Las reglas permiten definir quién tiene acceso de lectura, quien puede tener acceso de escritura, permite definir el formato de los datos (si un elemento puede tener hijos y definir el tipo de dato del elemento) y además permiten definir un elemento de los datos como índice para poder realizar búsquedas. Para configurar Reglas seleccionamos la pestaña Reglas en la consola de Firebase.

Reglas en la consola.

La estructura de las reglas debe de seguir la estructura de la base de datos, para que se puedan cumplir de manera adecuada. De igual manera, las reglas fallarán si no se tiene definida una regla en el padre.

Reglas de Escritura y Lectura

Existen dos tipos de reglas para regular el acceso a lectura y escritura de datos en nuestra base. Estas dos reglas son

  • .read: define las reglas que permiten la lectura de datos.
  • .write: define las reglas que permiten la escritura de datos.

Por default, está permitido hacer escritura y lectura de datos a los usuarios que están autenticados. Tenemos disponible la variable auth que es diferente de null cuando el usuario está autenticado. Al entrar a la sección de Base de Datos -> Reglas por primera vez, tenemos las siguientes reglas:

{
  "rules": {
  ".read": "auth != null",
  ".write": "auth != null"
  }
}

Las reglas tienen un formato tipo JSON empezando siempre con el nodo rules y donde cada elemento hijo de rules es una ruta diferente. Por ejemplo, nuestra base de datos tendrá una URL de la siguiente manera:

https://firebase-basededatos.firebaseio.com

Si queremos agregar un elemento /mensajes a nuestra base de datos y ponerle reglas, lo haríamos de la siguiente manera:

{
  "rules": {
      "mensajes": {
          ".read": "auth != null",
          ".write": "auth != null"
        }
  }
}

Esta regla define que para la ruta https://firebase-basededatos.firebaseio.com/mensajes todos los usuarios autenticados pueden leer y escribir datos.

Las reglas que definimos se aplican a todos los hijos; esto quiere decir que los hijos van a seguir las reglas que tenga el primer ancestro, sobreescribiendo las reglas definidas en los hijos.

Por ejemplo, en la siguiente regla, está definida una regla en el padre que permite escribir a cualquier usuario, sin necesitar autenticación. También tenemos definida en el hijo /mensajes otra regla que solamente permite a los usuarios autenticados escribir datos.

{
  "rules": {
      ".read": "auth == null",
      "mensajes": {
          ".read": "auth != null",
          ".write": "auth != null"
      }
  }
}

Como en Firebase se aplica la regla del primer ancestro, entonces cualquier usuario podría escribir datos en /mensajes.

Firebase también nos proporciona algunas variables que podemos utilizar, por ejemplo podemos hacer referencia a otras rutas de nuestra base de datos, tener acceso a identificadores de autenticación de usuarios, entre otras.

Un escenario común que podríamos encontrar, es solamente permitir que el usuario modifique su propia información. Para esto, podemos crear la variable $uid. Cada usuario podría tener asignada una URL, utilizando su identificador de usuario (obtenido al iniciar sesión con Firebase). Por ejemplo,

https://firebase-basededatos.firebaseio.com/usuarios/123ab45-123a-1234-abcde-a1abc123a1a1

Donde 123ab45-123a-1234-abcde-a1abc123a1a1 es el identificador del usuario. Si solamente queremos que ese usuario pueda leer datos de esa URL, definiríamos la siguiente regla:

{
    "rules": {
        "usuarios": {
            "$uid": {
                ".read": "$uid === auth.uid"
            }
        }
    }
}

Esta regla se leería de la siguiente manera: para la ruta /usuarios/$uid solamente podrán leer los datos de cada $uid los usuarios que se hayan autenticado y que tengan la misma uid después de autenticarse.

Más adelante veremos más variables que tenemos disponibles para definir las reglas.

Reglas de Validación

Podemos utilizar las reglas para validar los datos que se van a escribir a nuestra base. Es necesario tener estas reglas ya que la base de datos de Firebase no tiene esquema (al ser NoSQL). Con estas reglas podemos validar que los datos ingresados sean correctos y no pueda haber problemas posteriormente. Las reglas de validación se ejecutan una vez que se autorizó la escritura de los datos (se cumplieron las reglas de .write).

Para crear una regla de validación, debemos utilizar la palabra .validate. Como introducción, crearemos una regla que indique que unos valores serán números, cadenas, y limitaremos el tamaño de unas cadenas.

{
  "rules": {
      ".read": "auth != null",
      ".write": "true",
      "cadenas": {
        ".validate": "newData.isString()"
      },
      "numeros": {
        ".validate": "newData.isNumber()"
      },
      "cadenaTamaño": {
        ".validate": "newData.isString() && newData.val().length > 5"
      }
  }
}

Utilizando el simulador, podemos hacer pruebas y ver cuándo nuestras reglas se cumplen y cuando no.

Reglas en el simulador.
Reglas en el simulador.
Reglas en el simulador.

Más adelante veremos reglas más avanzadas.

Reglas para indexación

La base de datos en Firebase nos permite hacer búsquedas de los datos. Es importante crear índices para que estas búsquedas sean más eficientes, ya que la base puede contener una gran cantidad de datos.

Se utilizan las reglas para crear índices, con la palabra indexOn, proporcionando el nombre de los campos que queremos utilizar como índices:

"usuarios": {
  ".indexOn": ["apodo", "nombre"]
}

Con esta regla indexamos la ruta usuarios con sus nodos hijos apodo y nombre. Esta manera de indexar es indexar utilizando los nodos hijos.

Tambiéne podemos indexar utilizando los valores, en lugar de los hijos. Para esto utilizamos el valor .value

"indexOn": ".value"

Variables Disponibles para crear Reglas

Existen algunas variables predefinidas que podemos utilizar para crear nuestras reglas. Pueden ver la lista completa aquí.

auth

Como ya hemos visto, esta variable nos indica si el usuario ha sido autenticado y contiene la siguiente información:

  • provider: Nos indica qué método utilizó el usuario para iniciar sesión.
  • uid: El identificador único del usuario.
  • token: el token de autenticación del usuario.

Por ejemplo, si queremos que solamente puedan leer los datos usuarios que iniciaron sesión con Google, podemos definir una regla como la siguiente:

{
  "rules": {
    ".read": "auth.provider == 'google'",
    ".write": "auth != null"
  }
}

auth.token

Esta variable contiene la información del inicio de sesión del usuario. Puede o no tener las siguientes variables:

  • email: el correo electrónico utilizado.
  • email_verified: si el correo electrónico está verificado.
  • name: el nombre del usuario
  • firebase.sing_in_provider: el proveedor del inicio de sesión.

$variable

Como vimos anteriormente, se pueden definir variables en nuestra estructura de reglas con el símbolo $. Por ejemplo, habíamos definido la variable $uid para representar la ruta https://firebase-basededatos.firebaseio.com/usuarios/$uid donde $uid podría tener un valor similar a 123ab45-123a-1234-abcde-a1abc123a1a1.

Una vez definidas estas variables, podemos utilizarlas dentro de las reglas.

"$uid": {
    ".read": "$uid === auth.uid"
}

Esta regla se podría traducir de la siguiente manera:

"123ab45-123a-1234-abcde-a1abc123a1a1": {
    ".read": " 123ab45-123a-1234-abcde-a1abc123a1a1 === auth.uid"
}

Teniendo un valor diferente dependiendo del usuario que inicie sesión. De igual manera, podemos crear las variables que sean necesarias.

now

Esta variable contiene la cantidad de milisegundos transcurridos desde la fecha Epoch Unix (medianoche del 1 de enero de 1970 UTC) hasta el memento de la utilización. Podemos utilizar esta variable para crear reglas referentes al tiempo y fecha.

root

La variable root contiene una copia de los datos de la base como se encuentra antes de realizar la operación. Se utiliza para leer datos que existen previamente en la base y crear reglas basadas en esos datos.

data

Esta variable es similar a root, con la diferencia que solamente trae la copia de los datos en la ubicación que se está modificando. Por ejemplo,

{
  "rules": {
    "libros": {
      "$usuario": {
        ".read": "data.child('nombre').val() == 'John'"
      }
    }
  }
}

En este ejemplo, data contiene los datos que existen en $usuario. Por eso podemos tener acceso a su hijo (child) nombre y obtener su valor.

newData

De igual manera, newData es similar a las dos variables anteriores. En este caso, newData trae la información de los datos que se van a agregar. Esta variable solamente está disponible para las reglas .validate y .write.

Métodos importantes

Con las variables root, data y newData tenemos algunos métodos que nos permiten obtener información de la base de datos para crear nuestras reglas. Pueden consultar la lista completa aquí.

val()

Este método nos permite obtener el valor de un hijo. Los valores que se pueden obtener son String, Number, Boolean y Null.

child()

Este método nos permite tener acceso a un hijo determinado. Por ejemplo, data.child(‘nombre’) utilizado previamente nos da acceso al hijo “nombre” del elemento “$usuario”.

parent()

Con este método podemos movernos a algún nodo hermano.

data.parent().child('hijo')

hasChild(), hasChildren()

Estos dos métodos nos sirven para saber si root, data o newData tienen un hijo en específico o si tienen varios hijos. Nos puede servir para validar que newData tenga una estructura predefinida.

newData.hasChildren(['hijo1', 'hijo2'])

exists()

Nos indica si root, data, newData contienen algún dato o no.

isNumber(), isString(), isBoolean()

Nos indica si un valor es número, cadena o booleano.

Conclusión

Firebase nos proporciona la base de datos en tiempo real. Necesitamos definir reglas que nos indiquen quiénes tienen permisos para obtener los datos y quiénes tienen permisos para escribir en la base. También podemos tener índices para facilitar la búsqueda, que de igual manera se definen mediante las reglas. Por último, contamos con reglas para validar que los datos que se escriban en la base cumplan con ciertos valores.

En el próximo Post, crearemos una base de datos, implementaremos sus reglas, y utilizaremos una app de Android para escribir y leer en la base.

Deja un comentario