Miguel Castillo - 15 May 2020
Proyecto Final 2 de 3 - Fastify desde 0
Ahora que tenemos el proyecto base y nuestras claves de firebase vamos realizar la configuración de nuestra API REST. Vamos a crear 3 archivos distintos:
- routes.js: Donde vamos a tener nuestra rutas API.
- server.js: Donde vamos a definir la configuración del servidor.
- index.js: En donde vamosa definir el puerto donde se va a ejecutar nuestra aplicación.
Lo vamos a dividir de esta forma para que sea más fácil realizar los tests al final del proyecto, vamos entonces a empezar con server.js
.
Primero vamos a importar los modulos que necesitaremos, declararemos una función que construya el servidor y también vamos a importar las credenciales de nuestro archivo firebase.json
generado en la lección pasada:
const fastify = require("fastify");const path = require("path");// Paquete especial que instalamos la lección pasada para usar Firebaseconst admin = require("firebase-admin");// Podemos importar el archivo JSON como un objetoconst serviceAccount = require("./firebase.json");// Esta función va a crear nuestro servidorconst buildServer = (options) => {};// Exportamos nuestra función que crea el servermodule.exports = buildServer;
Ahora con firebase-admin
vamos a iniciar la instancia de Firebase para que lo podamos utilizar:
const buildServer = (options) => {admin.initializeApp({// Le pasamos todo el objeto como credencialescredential: admin.credential.cert(serviceAccount),// Obtenemos la URL de nuestra DB con la propiedad project_iddatabaseURL: `https://${serviceAccount.project_id}.firebaseio.com`,});// Guardamos la instancia de la base de datos en una variableconst db = admin.database();// También debemos guardar la referencia a raízconst ref = db.red("/");};
Para que podamos acceder a esa referencia que hemos creado vamos a utilziar lo aprendido creando un decorator
a nivel aplicación que estará disponible en cualquier ruta:
const buildServer = (options) => {admin.initializeApp({credential: admin.credential.cert(serviceAccount),databaseURL: `https://${serviceAccount.project_id}.firebaseio.com`,});const db = admin.database();const ref = db.ref("/");// Vamos a crear una instancia de Fastify con las opciones que le pasemos a la función buildServerconst server = fastify(options);/*Creamos el decorator llamado dbRef el cuál será una función que reciba como parámetro el nombre de la colección a la que deseamos acceder*/server.decorate("dbRef", (name) => ref.child(name));// En esta función vamos a retornar la instancia creada para utilizarla en los tests y en index.jsreturn server;};
Muy bien ahora vamos a crear nuestras rutas que se conectarán a Firebase.
Creación de rutas
Creamos el archivo routes.js
si no lo tuvimos antes y vamos a exportar un nuevo contexto que recibe como argumento la instancia de Fastify:
module.exports = async (fastify, opts) => {};
Procedemos a declarar nuestra primera ruta para validar la conexión a Firebase
module.exports = async (fastify, opts) => {fastify.get("/books", async (request, reply) => {// Accesamos a nuestro decorator creado// y utilizamos once de firebase-admin para obtener los// datosconst snapshot = await fastify.dbRef("books").once("value");// Enviamos los datos obtenidosreply.code(200).send(snapshot.val());});};
Ahora que tenemos la ruta declarada vamos a regresar a nuestro archivo server.js
para registrar routes.js
como un plugin:
// Continuamos server.js debajo de la declaración de decorateserver.decorate("dbRef", (name) => ref.child(name));// Registramos las rutas con el prefijo /api/v1server.register(require("./routes"), { prefix: "/api/v1" });// Seguirmos retornando la instanciareturn server;
Iniciar el servidor
Ahora vamos a crear el archivo index.js
para importar nuestra función que crea un servidor y escuchar solicitudes en el puerto que le asignemos:
const buildServer = require("./server");// Creamos nuestro server con un loggerconst server = buildServer({logger: {level: "info",prettyPrint: true,},});// Asignamos el puerto 3000 al server e iniciamosserver.listen(3000, (err) => {if (err) {server.log.error(err);process.exit(1);}console.log("Fastify corriendo en el puerto 3000");});
Ahora iniciamos el server ejecutando:
node index.js
Y consultamos la ruta http://localhost:3000/api/v1/books
en nuestro navegador o hacemos una solicitud GET en Postman y deberiamos obtener como respuesta la colección que declaramos en Firebase:
{"name": "Mi libro"}
Crear un libro
Vamos a definir las siguientes reglas para el schema
de validación de creación de libros:
- Requiere un campo string llamado
name
con mínimo 1 caracter. - Requiere un campo array llamado
author
que debe tener mínimo 1 elemento con las siguientes propiedades:name
que es el nombre del autor de tipo string y con mínimo 1 caracter.surname
que es el apellido del autor de tipo string y con mínimo 1 caracter.
- Requiere un campo integer llamado
year
que especificará el año de publicación de nuestro libro. - Requiere un campo llamada
synopsis
de tipo string con mínimo 1 caracter donde pondremos una breve descripción del libro.
Nuestra ruta con esquema quedaría de la siguiente forma:
// Archivo routes.js debajo de la primera ruta GET que declaramosfastify.post("/book",{schema: {body: {type: "object",required: ["name", "author", "year", "synopsis"],properties: {name: { type: "string", minLength: 1 },author: {type: "array",minItems: 1,items: {type: "object",required: ["name", "surname"],properties: {name: { type: "string", minLength: 1 },surname: { type: "string", minLength: 1 },},},},year: { type: "integer", minLength: 1 },synopsis: { type: "string", minLength: 1 },},additionalProperties: false,},},},// También podemos utilizar el handler como tercer argumento si// usamos el segundo argumento para sólo definir schemas y hooksasync (request, reply) => {// Obtenemos los campos de equest.bodyconst { name, author, year, synopsis } = request.body;// Esta es la sintaxis para crear un nuevo objeto en Firebase en nuestra colección booksconst bookCreated = await fastify.dbRef("books").push({ name, author, year, synopsis });// Retornaremos mensaje de nuevo libro agregado con la key que generó Firebasereply.code(200).send({message: "Nuevo libro agregado",key: bookCreated.key,});});
Con POSTMAN hacemos pruebas a la ruta /api/v1/book
mediante POST con un JSON como el siguiente ejemplo:
{"name": "El laberinto de la soledad","author": [{"name": "Octavio","surname": "Paz"}],"year": 1950,"synopsis": "El laberinto de la soledad es un ensayo publicado en 1950 por el escritor mexicano Octavio Paz."}
Deberiamos ver la respuesta JSON de nuestro server con una key generada:
{"message": "Nuevo libro agregado","key": "-M7K-08Mf1tWGxyCtL8f"}
Nota: La propiedad additionalProperties
como false
indica que toda propiedad que no esté definido en el schema
sea descartada de request.body
.
Si volvemos a consultar la ruta GET /api/v1/books
vamos a obtener el libro que generamos en Firebase:
{"-M7K-08Mf1tWGxyCtL8f": {"author": [{"name": "Octavio","surname": "Paz"}],"name": "El laberinto de la soledad","synopsis": "El laberinto de la soledad es un ensayo publicado en 1950 por el escritor mexicano Octavio Paz.","year": 1950},"name": "Mi libro"}
Observamos que ya tenemos el libro que creamos sin embargo tenemos igual el campo "name" "Mi libro" que creamos de prueba en Firebase. Podemos ir a nuestra base de datos otra vez en Firebase a eliminar ese registro:
Busca más libros que conozcas o datos de prueba para tener más datos en Firebase que podamos modificar y eliminar.
Consultar un registro por su key
En nuestro archivo routes.js
vamos a agregar una nueva ruta para consultar un libro por su key en especifico.
fastify.get("/book/:key",{schema: {params: {type: "object",required: ["key"],properties: {key: { type: "string", minLength: 4 },},},},},async (request, reply) => {const key = request.params.key;const snapshot = await fastify.dbRef(`books/${key}`).once("value");reply.code(200).send(snapshot.val());});
Para esta ruta especificamos una URL con el parámetro dinámico llamado key
con un schema
con un tamaño mínimo de 4. Esta ruta va a consultar ese objeto en especifico en Firebase mediante la key que enviamos. Por ejemplo la key que generó el libro de "El laberinto de la soledad" fue -M7K-08Mf1tWGxyCtL8f
vamos a reiniciar la aplicación y consultamos la ruta GET /api/v1/book/-M7K-08Mf1tWGxyCtL8f
Con esto deberiamos obtener como respuesta el libro en especifico:
{"author": [{"name": "Octavio","surname": "Paz"}],"name": "El laberinto de la soledad","synopsis": "El laberinto de la soledad es un ensayo publicado en 1950 por el escritor mexicano Octavio Paz.","year": 1950}
Modificar un registro
Ya tenemos libros disponibles para consulta, ahora vamos a especificar una ruta en routes.js
para modificar alguna propiedad de un libro en específico, reutilizamos el esquema que definimos para POST en el body y para el GET en params:
fastify.put("/book/:key",{schema: {params: {type: "object",required: ["key"],properties: {key: { type: "string", minLength: 4 },},},// A diferencia en POST no haremos campos requeridosbody: {type: "object",additionalProperties: false,properties: {name: { type: "string", minLength: 1 },author: {type: "array",minItems: 1,items: {type: "object",required: ["name", "surname"],properties: {name: { type: "string", minLength: 1 },surname: { type: "string", minLength: 1 },},},},year: { type: "integer" },synopsis: { type: "string", minLength: 1 },},},},},async (request, reply) => {const key = request.params.key;// Obtenemos los datos actuales de esa keyconst snapshot = await fastify.dbRef(`books/${key}`).once("value");const value = snapshot.val();// Si existe el registro lo modificaremosif (value) {/* Hacemos una combinacion de los campos actuales y los campos nuevos solo para modificar los campos que fueron enviados*/const data = await fastify.dbRef(`books/${key}`).update({...value,...request.body,});// Retornamos el mensaje con los nuevos valores del libroreply.code(200).send({message: "El libro se ha actualizado",key,book: {...value,...request.body,},});} else {// Si no se encuentra el libro devolvemos el error 400reply.code(400).send({message: "El libro no existe",});}});
Con la misma key de nuestro libro generado vamos a modificar el año del libro, en POSTMAN hacemos un nuevo request a la ruta PUT /api/v1/book/-M7K-08Mf1tWGxyCtL8f
con los siguiente datos JSON de ejemplo:
{"year": 1960}
Deberiamos obtener una respuesta de la siguiente forma:
{"message": "El libro se ha actualizado","key": "-M7K-08Mf1tWGxyCtL8f","book": {"author": [{"name": "Octavio","surname": "Paz"}],"name": "El laberinto de la soledad","synopsis": "El laberinto de la soledad es un ensayo publicado en 1950 por el escritor mexicano Octavio Paz.","year": 1960}}
Si volvemos a consultar ese libro en especifico vamos a ver que el año del libro ya fue modificado.
Eliminar un registro
En nuestra última ruta vamos a eliminar el libro por la key que enviamos como parámetro. De la misma forma vamos a hacer un schema
para validar ese parámetro en routes.js
:
fastify.delete("/book/:key",{schema: {params: {type: "object",required: ["key"],properties: {key: { type: "string", minLength: 4 },},},},},async (request, reply) => {const key = request.params.key;const snapshot = await fastify.dbRef(`books/${key}`).once("value");const value = snapshot.val();// Consultamos y verificamos si existe el libroif (value) {// Si existe el libro lo eliminamosawait fastify.dbRef(`books/${key}`).remove();reply.code(200).send({message: "El libro ha sido eliminado",key,});} else {reply.code(400).send({message: "Libro no válido",});}});
Vamos a eliminar el libro de "El laberinto de la soledad" reiniciando nuestra aplicación y enviando un request en POSTMAN de tipo DELETE a la ruta /api/v1/book/-M7K-08Mf1tWGxyCtL8f
, este va a eliminar ese registro de nuestra colección y ya no lo tendremos disponible en el listado de libros.
Ya tenemos completa nuestra API REST, en la siguiente lección vamos a integrar el proyecto React a nuestra aplicación y a realizar algunas configuraciones adicionales.