Laboratorio Firebase Cloud Functions REST API CRUD
Descripción
En este laboratorio vamos a aprender a utilizar las Cloud Functions de Firebase para crear una REST API que nos permita realizar operaciones CRUD sobre una colección de documentos en Firestore.
Requisitos
- Cuenta en Firebase
- Cuenta en GitHub
- Node.js
- npm
Desarrollo
Paso 1: Crear un proyecto en Firebase
- Accede a la Consola de Firebase.
- Haz clic en el botón Crear un proyecto.
- Ingresa el nombre de tu proyecto y haz clic en el botón Continuar.
En este proyecto no es necesario activar Google Analytics.
- Aceptamos las condiciones y hacemos clic en el botón Crear proyecto.
Esperamos un momento hasta que se cree el proyecto.
Finalmente debe aparecernos de la siguiente manera.
Con estos pasos podemos ver al lado izquierdo algunas de las opciones que nos ofrece Firebase.
Dentro de las opciones más destacadas tenemos:
- Authentication: Nos permite autenticar a los usuarios de nuestra aplicación.
- Database: Nos permite almacenar datos en tiempo real.
- Firestore: Nos permite almacenar datos en la nube.
- Storage: Nos permite almacenar archivos en la nube.
- Functions: Nos permite ejecutar código en la nube.
- Hosting: Nos permite alojar nuestra aplicación web.
De las opciones antes mencionadas utilizaremos Firestore y Functions.
Paso 2: Inicializar un proyecto de Node.js con Firebase
Creamos un nuevo directorio en nuestro sistema operativo, para este ejemplo lo llamaremos practica_firebase_functions.
Ingresamos al directorio que acabamos de crear con Visual Studio Code.
Abrimos una terminal en Visual Studio Code.
- Necesitamos instalar Firebase CLI, para ello ejecutamos el siguiente comando:
npm install -g firebase-tools
El comando anterior instala Firebase CLI de forma global en nuestro sistema operativo.
- Ahora nos conectamos Firebase con nuestro proyecto de Node.js ejecutando el siguiente comando:
firebase login
Lo primero que nos pedirá es una serie de preguntas para asegurarse de que estamos conectando Firebase con el proyecto correcto.
? Allow Firebase to collect CLI and Emulator Suite usage and error reporting information? (Y/n)
En el caso de que no queramos compartir esta información con Firebase, podemos responder con la letra n, pero se sugiere responder con la letra Y.
Esto nos pedirá que mediante nuestro navegador iniciemos sesión con nuestra cuenta de Google, se sugiere utilizar la cuenta institucional, sin embargo, se puede utilizar cualquier cuenta de Google.
Concedemos los permisos necesarios para que Firebase pueda acceder a nuestra cuenta de Google.
Finalmente nos aparecerá un mensaje en la terminal indicando que hemos iniciado sesión correctamente.
- Inicializamos un proyecto de Firebase ejecutando el siguiente comando:
firebase init
Esto nos mostrará una serie de opciones que debemos seleccionar:
? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm
your choices. (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
❯◯ Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance
◯ Firestore: Configure security rules and indexes files for Firestore
◯ Functions: Configure a Cloud Functions directory and its files
◯ Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
◯ Hosting: Set up GitHub Action deploys
◯ Storage: Configure a security rules file for Cloud Storage
◯ Emulators: Set up local emulators for Firebase products
(Move up and down to reveal more choices)
Para este laboratorio seleccionamos la opción Functions, lo hacemos con la tecla de espacio y luego presionamos la tecla Enter.
Esto nos llevará a la siguiente pantalla.
=== Project Setup
First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.
? Please select an option: (Use arrow keys)
❯ Use an existing project
Create a new project
Add Firebase to an existing Google Cloud Platform project
Don't set up a default project
Seleccionamos la opción Use an existing project y presionamos la tecla Enter.
Dentro de las opciones nos aparecerá los proyectos que tenemos en Firebase, seleccionamos el proyecto que creamos en el paso 1 y presionamos la tecla Enter.
=== Project Setup
First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.
? Please select an option: Use an existing project
? Select a default Firebase project for this directory: (Use arrow keys)
❯ fir-functions-796c0 (firebase-functions)
Ahora nos sugerirá el lenguaje de programación que vamos a utilizar.
=== Functions Setup
Let's create a new codebase for your functions.
A directory corresponding to the codebase will be created in your project
with sample code pre-configured.
See https://firebase.google.com/docs/functions/organize-functions for
more information on organizing your functions using codebases.
Functions can be deployed with firebase deploy.
? What language would you like to use to write Cloud Functions? (Use arrow keys)
❯ JavaScript
TypeScript
Python
Para este laboratorio seleccionamos la opción JavaScript y presionamos la tecla Enter.
Finalmente nos sugiere instalar ESLint, es un linter que nos ayuda a mantener un código limpio y ordenado.
Si lo activa, posiblemente tenga inconvenientes en la etapa de Deploy, para esta práctica no es requerido, sin embargo en un entorno de producción es recomendable.
? Do you want to use ESLint to catch probable bugs and enforce style? (y/N)
Como ultimo paso nos sugiere instalar las dependencias necesarias para el proyecto.
✔ Wrote functions/package.json
✔ Wrote functions/.eslintrc.js
✔ Wrote functions/index.js
✔ Wrote functions/.gitignore
? Do you want to install dependencies with npm now? (Y/n)
Para este laboratorio seleccionamos la opción Y y presionamos la tecla Enter.
Con todos estos pasos hemos creado una estructura de proyecto de Firebase con Cloud Functions.
Crear nuestro CRUD en nuestro proyecto de Firebase
- Dentro de la carpeta functions nos dirigimos al archivo index.js.
Para probar que todo está funcionando correctamente, vamos a modificar el archivo index.js con el siguiente código:
const functions = require('firebase-functions');
const express = require('express');
const app = express();
.get('/hello-world', (req, res) => {
app.send('Hello World');
res;
})
.app = functions.https.onRequest(app); exports
En el código anterior estamos creando una función que nos permite acceder a la ruta /hello-world y nos devuelve un mensaje Hello World.
- Para probar que todo está funcionando correctamente, ejecutamos el siguiente comando:
firebase serve
Este comando nos permitirá ejecutar nuestra aplicación en un servidor local en el puerto 5000.
Para que todo funcione correctamente debemos acceder a la siguiente URL en nuestro navegador http://localhost:5000/fir-functions-796c0/us-central1/app/hello-world
Con ello podemos ver el mensaje Hello World.
Ahora lo que queremos no es un hello world, sino un CRUD, para ello vamos a crear una colección de documentos en Firestore.
Para ello el siguiente paso será autenticar nuestra función con Firebase.
Paso 3: Autenticar nuestra función con Firebase
Dentro de la carpeta functions nos dirigimos al archivo index.js.
Agregamos el siguiente código al inicio del archivo index.js.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const express = require('express');
const app = express();
const admin.inicializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: 'https://fir-functions-796c0.firebaseio.com'
;
})
.get('/hello-world', (req, res) => {
app.send('Hello World');
res;
})
.app = functions.https.onRequest(app); exports
Este código lo obtenemos desde la configuración de nuestro proyecto en Firebase.
En esta sección nos vamos a dirigir a Cuentas de Servicio.
Como podemos observar en la imagen anterior, nos aparece un mensaje que nos indica que no tenemos ninguna cuenta de servicio, por lo que debemos crear una.
Actualmente Firebase tiene soporte para Node.js, Java, Python y Go.
En nuestro caso vamos a elegir Node.js.
var admin = require("firebase-admin");
var serviceAccount = require("path/to/serviceAccountKey.json");
.initializeApp({
admincredential: admin.credential.cert(serviceAccount)
; })
Como se puede observar en el codigo anterior en la sección de serviceAccountKey.json debemos colocar la ruta de nuestro archivo de credenciales, este archivo lo obtenemos en el botón Generar nueva clave privada.
Al hacer clic en el botón Generar nueva clave privada se descargará un archivo .json que contiene las credenciales de nuestra cuenta de servicio.
Lo descargamos y lo guardamos en la carpeta functions, podemos renombrarlo para facilitar su uso.
Si todo salio bien finalmente debemos tener un código como el siguiente:
const functions = require('firebase-functions');
const admin = require("firebase-admin");
const express = require('express');
const bodyParser = require('body-parser');
const serviceAccount = require("./permisos.json");
const app = express();
.initializeApp({
admincredential: admin.credential.cert(serviceAccount)
;
})
const db = admin.firestore();
.use(bodyParser.json());
app
.get('/hello-world', (req, res) => {
app.send('Hello World');
res;
})
.post('/api/products', async (req, res) => {
apptry {
if (!req.body.id || !req.body.name) {
return res.status(400).send({error: 'ID and Name are required'});
}
await db.collection('products')
.doc(req.body.id)
.set({name: req.body.name});
return res.status(204).send();
catch (error) {
} console.error("Error adding document: ", error);
return res.status(500).send({error: 'Internal Server Error'});
};
})
.app = functions.https.onRequest(app); exports
Vamos a analizar un poco el código anterior:
admin.initializeApp: Inicializa la aplicación de Firebase con las credenciales de nuestra cuenta de servicio.
db: Es una instancia de Firestore que nos permite interactuar con la base de datos.
app.use(bodyParser.json()): Nos permite recibir datos en formato JSON.
app.post(‘/api/products’): Nos permite crear un documento en Firestore.
req.body.id: Es el identificador del documento.
req.body.name: Es el nombre del documento.
db.collection(‘products’).doc(req.body.id).set({name: req.body.name}): Crea un documento en la colección products con el identificador req.body.id y el nombre req.body.name.
res.status(204).send(): Nos devuelve un código de estado 204 que indica que la operación fue exitosa.
res.status(500).send({error: ‘Internal Server Error’}): Nos devuelve un código de estado 500 que indica que hubo un error en el servidor.
En este caso estamos utilizando el método POST para crear un documento en Firestore, sin embargo, podemos utilizar los métodos GET, PUT y DELETE para realizar operaciones de lectura, actualización y eliminación respectivamente.
Para poder probarlo podemos utilizar un cliente como Thunder Client, Postman, Insomnia, etc.
El paso anterior no nos funcionará mientras no tengamos una colección en Firestore.
Paso 4: Crear un documento en Firestore
- Para probar que todo está funcionando correctamente, debemos crear un documento en Firestore,
- Dentro de la consola de Firebase, nos dirigimos a la opción Firestore.
- Hacemos clic en el botón Crear base de datos.
- Seleccionamos la opción Iniciar en modo de prueba y hacemos clic en el botón Siguiente.
Si todo salio bien debemos tener una salida como la siguiente.
Hagamos una segunda prueba
Ahora vamos a crear el CRUD completo.
Empezamos por el método POST.
// Create a new product
.post('/api/products', async (req, res) => {
apptry {
const { id, name } = req.body;
if (!id || !name) {
return res.status(400).send({ error: 'ID and Name are required' });
}
await db.collection('products').doc(id).set({ name });
return res.status(204).send();
catch (error) {
} console.error("Error adding document: ", error);
return res.status(500).send({ error: 'Internal Server Error' });
}; })
En el código anterior estamos creando un nuevo producto en la colección products. Para ello necesitamos enviar un objeto JSON con los campos id y name. Ejemplo
{
"id": "1",
"name": "Product 1"
}
Si todo sale bien, nos devolverá un código de estado 204.
Continuamos con el método GET.
// Get all products
.get('/api/products', async (req, res) => {
apptry {
const querySnapshot = await db.collection('products').get();
const products = querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
return res.status(200).send(products);
catch (error) {
} console.error("Error getting documents: ", error);
return res.status(500).send({ error: 'Internal Server Error' });
};
})
// Get a product by ID
.get('/api/products/:id', async (req, res) => {
apptry {
const doc = await db.collection('products').doc(req.params.id).get();
if (!doc.exists) {
return res.status(404).send({ error: 'Product not found' });
}return res.status(200).send({ id: doc.id, ...doc.data() });
catch (error) {
} console.error("Error getting document: ", error);
return res.status(500).send({ error: 'Internal Server Error' });
}; })
En el código anterior estamos obteniendo todos los productos de la colección products y un producto en específico por su id.
Para obtener todos los productos debemos acceder a la siguiente URL http://localhost:5000/fir-functions-796c0/us-central1/app/api/products
Para obtener un producto en específico debemos acceder a la siguiente URL http://localhost:5000/fir-functions-796c0/us-central1/app/api/products/1
Continuamos con el método PUT.
// Update a product by ID
.put('/api/products/:id', async (req, res) => {
apptry {
const { name } = req.body;
if (!name) {
return res.status(400).send({ error: 'Name is required' });
}
await db.collection('products').doc(req.params.id).update({ name });
return res.status(204).send();
catch (error) {
} console.error("Error updating document: ", error);
return res.status(500).send({ error: 'Internal Server Error' });
}; })
En el código anterior estamos actualizando un producto en la colección products por su id.
Para actualizar un producto debemos enviar un objeto JSON con el campo name. Ejemplo
{
"name": "Producto actualizado"
}
Si todo sale bien, nos devolverá un código de estado 204.
Finalmente continuamos con el método DELETE.
// Delete a product by ID
.delete('/api/products/:id', async (req, res) => {
apptry {
await db.collection('products').doc(req.params.id).delete();
return res.status(204).send();
catch (error) {
} console.error("Error deleting document: ", error);
return res.status(500).send({ error: 'Internal Server Error' });
}; })
En el código anterior estamos eliminando un producto en la colección products por su id.
Para eliminar un producto debemos acceder a la siguiente URL http://localhost:5000/fir-functions-796c0/us-central1/app/api/products/1 con el método DELETE.
Si todo sale bien, nos devolverá un código de estado 204.
Con estos pasos hemos creado una REST API que nos permite realizar operaciones CRUD sobre una colección de documentos en Firestore.
Paso 5: Desplegar nuestra aplicación en Firebase
Antes de desplegar nuestra aplicación en Firebase, debemos realizar algunos cambios en nuestro proyecto.
- Dentro de la carpeta functions encontraremos el archivo .gitignore, debemos agregar una linea que nos permita eliminar nuestro archivo de credenciales.
'''
permisos.json
- Para desplegar nuestra aplicación en Firebase, ejecutamos el siguiente comando:
firebase deploy
Este comando nos permitirá desplegar nuestra aplicación en Firebase.
Toma en cuenta que este último paso necesita de adquirir un plan de pago en Firebase.
Por lo tanto no será necesario realizar este paso.
Podemos subir nuestro proyecto a GitHub para que los demás puedan ver nuestro código.
Eso es todo, hemos creado una REST API que nos permite realizar operaciones CRUD sobre una colección de documentos en Firestore.
Conclusiones
- Firebase Cloud Functions nos permite ejecutar código en la nube.
- Firebase Firestore nos permite almacenar datos en la nube.
- Podemos utilizar Firebase Cloud Functions y Firebase Firestore para crear una REST API que nos permita realizar operaciones CRUD sobre una colección de documentos en Firestore.