Firebase (versión 9)

Configuración en la página web de Firebase

Crear un proyecto de Firebase

  1. Vamos a http://firebase.com/.
  2. Creamos un nuevo proyecto → Le ponemos un nombre.

Crear una base de datos en Firebase

Menu Build → Firestone.

Por hacer un simil con las bases de datos relacionales:

  • Cada collection equivaldría a una tabla.
  • Cada document equivaldría a un registro.

Cuando estructuramos una base de datos usando firebase, debemos tener en cuenta que las colecciones tendrán documentos y que esta relación será jerárquica. Debemos evitar en la medida de lo posible que una colección se relacione con tra colección de un modo no jerárquico como ocurría en las bases de datos relacionales tradicionales.

Firebase (versión 9) 1

Tendremos que configurar una regla para permitir la edición y consulta. Para ello iremos a Firestore Rules

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

Características de los datos almacenada en Firestore

  • A la entidad que los contiene se la llama Documento.
  • A una agrupación de datos se la llama Colección.
  • Un Documento puede a su vez tener Colecciones.
  • Tanto los documentos como las colecciones tienen un tamaño máximo de 1Mb.
  • Podemos tener hasta 100 niveles de sub-colecciones.
  • Son almacenados siguiendo una estructura clave-valor.
  • No pueden ocupar más de 1MB.
  • Los datos pueden ser de los siguientes tipos: string, number, boolean, map, array, null, timestamp, geopoint.

Configuración en el código de React

npm i firebase

Configuramos las credenciales necesarias:

/.env

REACT_APP_PROJECT_ID = aqui_vendria_el_project_id_de_firebase
REACT_APP_API_KEY = aqui_vendria_la_clave_api_key_de_firebase

/src/app/firebase.js

import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { getAuth, setPersistence, browserLocalPersistence } from 'firebase/auth';

const firebaseConfig = {
    apiKey: process.env.REACT_APP_API_KEY,
    authDomain: process.env.REACT_APP_PROJECT_ID + '.firebaseapp.com',
    projectId: process.env.REACT_APP_PROJECT_ID,
    storageBucket: process.env.REACT_APP_PROJECT_ID + ".appspot.com",
};

const firebaseApp = initializeApp(firebaseConfig);
export const db = getFirestore();
export const auth = getAuth(firebaseApp);

// Si descomentas la siguiente línea, cuando mientras que el usuario no se desloguee expresamente o cierre el navegador, permanecerá logueado y podremos acceder a su id desde cualquier página
setPersistence(auth, browserLocalPersistence);

Podemos obtener la projectId y el apiKey yendo a engranaje que esta al lado de Project Settings → General. Para que la API Key se visualiza tengo que haber inicializado la autentificación ( Firebase → Authentification → Get Started).

El authDomain será {ProjectId}.firebaseapp.com

CRUD en Firebase

/src/app/api.js

import { collection, getDocs, query, doc, getDoc, addDoc, deleteDoc, updateDoc, setDoc, where } from "firebase/firestore";
import { db } from './firebase';

// CREATE
export const createItem = async(obj) => {
    const colRef = collection(db, 'items');
    const data = await addDoc(colRef, obj);
    return data.id;
}

// UPDATE
export const updateItem = async (id, obj) => {
    const colRef = collection(db, 'items');
    await updateDoc(doc(colRef, id), obj)
}

// READ
export const getItems= async ()  => {
    const colRef = collection(db, 'items');
    const result = await getDocs(query(colRef));
    return getArrayFromCollection(result);
}

// READ WITH WHERE
// Tener en cuenta que el tipo de dato de la condición debe coincidir con el tipo de dato que hay en Firebase o no obtendré un dato de respuesta
export const getItemsByCondition = async (value) => {
    const colRef = collection(db, 'items');
    const result = await getDocs(query(colRef, where('age', '==', value)));
    return getArrayFromCollection(result);
}

export const getItemById = async (id) => {
    const colRef = collection(db, 'items');
    const result = await getDoc(doc(colRef, id));
    return result.data();
}

// DELETE
export const deleteItem = async (id) => {
    const colRef = collection(db, 'items');
    await deleteDoc(doc(colRef, id));
}

const getArrayFromCollection = (collection) => {
    return collection.docs.map(doc => {
        return { ...doc.data(), id: doc.id };
    });
}

Batch Update

Nos permitirá realizar múltiples operaciones de manera atómica, o todas o ninguna.

Un ejemplo de uso puede ser cuando hacer una transacción bancaria, en la que sacas dinero de una cuenta y lo metes en otra, en este caso quieres que se realicen ambas operaciones o que en el peor de los casos no se realice ninguna.

export const batchOperation = async (id) => {
    const batch = writeBatch(db)

    batch.update(doc(db, 'persons', id), { name: 'test' });
    batch.update(doc(db, 'persons', id), { price: 89 });
    batch.commit();
};

Consultar los documents de una colección a partir de su id

const result = await getDocs(query(studentsBaseRef, where(documentId(), 'in', students.map(student => student.id))));

Juntar los datos de dos documents diferentes

const mergedData = students.map(student => {
    const studentInClassroom= studentsInClassroom.find(({ id }) => student.id === id);
    return { ...student, ...studentInClassroom};
});

Ejercicios Firebase

Ejercicio CRUD

Hacer el ejercicio del enlace, que cuenta con las funcionalidades de alta, baja, modificación y consulta de nombres de personas.

Ejercicio Lista de invitados

Hacer una página web para una lista de invitados con 3 secciones. Una para consultar los invitados, otra para dar de alta un nuevo invitado y otra para darlo de baja.

Cada uno de los siguientes pantallazos corresponde a una url diferente, por lo que usaremos react router dom.

Firebase (versión 9) 3
Firebase (versión 9) 4
Firebase (versión 9) 5

Ejercicio Hospital

La siguiente aplicación estará compuesta de cuatro páginas indpendientes, cuya ruta gestionaremos con React Router:

  • Home → con el menú principal.
  • Create → para dar de alta un nuevo paciente.
  • Read → muestra un listado con los pacientes del hospital. Al pulsar sobre la id de alguno de ellos, seremos redirigidos a la página de update y delete, en la que podremos actualizar y borrar un registro.
  • Update y Delete → esta página se rellenará con los datos del usuario seleccionado en la página Read. Desde aquí podremos modificar el registro introduciendo nuevos datos, o eliminarlo.
Firebase (versión 9) 6
Firebase (versión 9) 7
Firebase (versión 9) 8
Firebase (versión 9) 9

Ejercicio Libros

1. Creamos una tabla con una sola fila. En esta fila, habrá dos inputs. En uno de ellos introduciremos el título del libro y en la otra el precio.

Firebase (versión 9) 10

Cuando pulsemos el botón de añadir, el libro quedará almacenado en Firebase.

2. Consultamos los libros que han en la base de datos mostrando un registro correspondiente a cada libro. Cuando introduzcamos un nuevo libro, el listado debe actualizarse.

Firebase (versión 9) 11

3. Añadimos la posibilidad de borrar un libro pulsando el botón Remove.

Firebase (versión 9) 12

4. Vamos ahora a hacer la actualización. Para poder actualizar los datos de cada fila de manera independiente, haremos un componente llamado Tr (table row) que tendrá toda la presentación y la lógica necesaria.

Firebase (versión 9) 13
Firebase (versión 9) 14

Lista de tareas

1. En la Home de esta aplicación de mensajería introduciremos nuestro nombre. Si ese nombre no existía en la base de datos, lo introduciremos. Para ello utilizaremos el siguiente código:

export const access = async (name) => {
    const result = await getDocs(query(usersCollection, where('name', '==', name)));
    if (result.size === 0) {
        const a = await addDoc(usersCollection, { name });
        return a.id;
    }
    return result.docs[0].id;
}
Firebase (versión 9) 15

2. Tanto si el nombre no existía como si existía, recuperaremos su id y lo salvaremos en la memoria global utilizando ContextApi.

3. En la pantalla de tareas podremos crear nuevas tareas o ver las existentes:

Firebase (versión 9) 16

En la base de datos de firebase tendremos una entidad llamada user que a su vez tendrá una colección de tasks.

4. Añade la posibilidad de eliminar tareas existentes.

5. Añade la posiblidad de actualizar tareas existentes. Para ello, tendremos un botón de actualizar que mostrará el texto de la tarea dentro de un input. Pulsando el botón de actualizar realmente, podremos modificar la tarea en la base de datos.

Firebase (versión 9) 17
Firebase (versión 9) 18

Foro

En la carpeta services, crearemos dos ficheros: hilos y users. La entidad hilos tendrá en su interior un array de comentarios.

Página de crear un hilo.

Firebase (versión 9) 19

Página de ver hilos.

Firebase (versión 9) 20

Página de ver la información de un hilo.

Firebase (versión 9) 21

Este ejercicio se puede ampliar de muchas formas:

  • Indicando el nombre de la persona que ha escrito el comentario.
  • Indicando la fecha de la publicación.
  • Añadiendo una capa de diseño.

Mensajería

En la Home de esta aplicación de mensajería introduciremos nuestro nombre. Si ese nombre no existía en la base de datos, lo introduciremos. Para lograr esto, podemos utilizar el siguiente código:

export const access = async (name) => {
    const result = await getDocs(query(usersCollection, where('name', '==', name)));
    if (result.size === 0) {
        const a = await addDoc(usersCollection, { name });
        return a.id;
    }
    return result.docs[0].id;
}

Tanto si el nombre no existía como si existía, recuperaremos su id y lo salvaremos en la memoria global utilizando ContextApi.

Firebase (versión 9) 22

Para gestionar las peticiones tendremos dos ficheros independientes. Uno para gestionar las peticiones referentes a los usuarios y otro para gestionar las peticiones referentes a los mensajes.

Firebase (versión 9) 23

En la pantalla de escribir mensaje escibiremos el mensaje que queremos enviar y marcaremos la check correspondiente a cada uno de los destinatarios.

Firebase (versión 9) 24

En la pantalla de ver mensajes podremos ver los mensajes que nos han enviado.

En la página de write mesage tendremos un estado que contendrá un array con las id’s correspondientes a los usuarios marcados. Para alimentar ese array en función de las checkboxes marcadas, usaremos el siguiente código:

// Cada vez que una checkbox cambia, se actualiza el array de usuarios seleccionados
const onChange = (userId) => {
    if (checkedUsersIds.includes(userId)) {
        setCheckedUsersIds(checkedUsersIds.filter(id => userId != id))
    } else {
        setCheckedUsersIds([...checkedUsersIds, userId]);
    }
}

Cada mensaje tendrá tres propiedades: idRemitente, idUsuario y msg:

Firebase (versión 9) 25
Firebase (versión 9) 26

Autentificación

Autentificación con mail y password

Con el método setDoc podremos guardar en la colección «users» un documento cuya especificándole expresamente cual será su id. En este caso hemos seleccionado que la id del documento sea la id del usuario de Firebase.

import { signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut } from "firebase/auth";
import { auth } from "./firebase";

export const signUp = async (email, password) => {
    try {
        const userCredential = await createUserWithEmailAndPassword(auth, email, password);
        const user = userCredential.user;
        await setDoc(doc(db, 'users', user.uid), {});
        return user.uid;
    } catch (err) {
        return err.message;
    }
}

export const signIn = async (email, password) => {
    try {
        const result = await signInWithEmailAndPassword(auth, email, password);
        return result.user.uid;
    } catch (err) {
        return err.message;
    }
}

export const getCurrentUserId = async () => await auth.currentUser?.uid;
export const logout = async () => await signOut(auth);

Autentificación con cuenta de Google

Este comando mostrará un popup a través del cual podremos autentificarnos.

export const loginWithGoogle = () => {
    const provider = new GoogleAuthProvider();
    return signInWithPopup(auth, provider).then(result => {
        return result.user;
    });
}

Hacer una acción cuando el usuario se loguee/desloguee

import { onAuthStateChanged } from 'firebase/auth';
...

const [isLogged, setIsLogged] = useState(false);

useEffect(() => {
    onAuthStateChanged(auth, user => {
        if (user) {
            console.log('user', user);
            const uid = user.uid;
            setIsLogged(true);
        } else {
            console.log("No user logged");
                setIsLogged(false);
        }
    });
}, []);

A tener en cuenta en la autentificación

Hacer todo el proceso de autentificación y pulirlo hasta la perfección puede llevar meses. La validacción de la cuenta de correo haciendo click en el correo electrónico que nos llega, la recuperación de la contraseña, las pantallas vinculadas al login y registro. Firebase cuenta con métodos para realizar todo el proceso, sin embargo serás tu quien deberá implementarlas para realizar todo el sistema de autentificación y registro.

Para ahorrarnos todo este proceso, el servicio auth0 ya tiene todas las pantallas funciones integradas y sólo tendremos que añadirlo a nuestro desarrollo.

Tiempo real en Firebase

La siguiente función envía a la vista un array con los datos de la colección actualizada.

export const onTextUpdated = (callback) => onSnapshot(itemsCollection, (docs) => {
    callback(getArrayFromCollection(docs));
});

Código en la vista:

  useEffect(() => {
    getMessages();
    onTextUpdated((messages) => {
      setItems(messages);
    });
  }, []);

Guardar medios en Firebase

En el área de administración de firebase, iremos a menú StorageRules → Seteamos el valor a true:

rules_version = '2';
service firebase.storage {
   match /b/{bucket}/o {
      match /{allPaths=**} {
         allow read, write: if true;
      }
   }
}
firebase.js
import { getStorage } from 'firebase/storage';

const firebaseConfig = {
    ...
    storageBucket: process.env.REACT_APP_PROJECT_ID + ".appspot.com",
};

export const storage = getStorage(firebaseApp);

import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';

export const uploadImage = async (file, uid) => {
    const storageRef = ref(storage, `/files/${uid}/${file.name}`);
    const uploadTask = uploadBytes(storageRef, file);
    uploadTask.then(async (data) => {
        const url = await getDownloadURL(data.ref);
        const colRef = collection(db, "students");
        await updateDoc(doc(colRef, uid), { uploadedPicture: url });
    })
}

Relación muchos a muchos

Suponemos que nuestra aplicación utiliza una relación muchos a muchos. Por ejemplo, imagina que tenemos dos colecciones: usuarios y eventos. Un usuario puede tener muchos eventos y un evento puede tener muchos usuarios. Para gestionar esa relación crearemos una tabla intermedia en la que cada documento tendrá dos propiedades:

  • La id del usuario.
  • La id del evento.
← Compilar (build o publicar) una aplicación de React
Login con Auth0 →