This is an old revision of the document!
Table of Contents
Desarrollo de aplicaciones: backend + frontend
Configuración mínima de un proyecto
Crear un fichero package.json:
{ "name": "cities-backend", "version": "0.1", "description": "A sample project to learn Node.js", "type": "module", "scripts": { "start": "node src/app.js" }, "author": "Santiago Faci", "license": "GPL-2.0-only", "dependencies": { "express": "^4.21.1", "cors": "^2.8.6" } }
Algunos comandos útiles:
npm init: Crear un ficheropackage.jsonde forma interactiva (si se añade–yesse rellenará directamente con valores por defecto)npm run: Lista los scripts disponiblesnpm run <nombre_script: Ejecuta el script cuyo nombre se para como parámetronpm start: Ejecuta el scriptstart(se puede ejecutar sin añadirrunal tratarse de uno de los scripts definidos por nodejs)
Gestión de dependencias
Añadir las dependencias necesarias para el proyecto al fichero package.json en la sección dependencies (se pueden ir añadiendo a medida que son necesarias durante el desarrollo del proyecto):
. . . "dependencies": { "express": "^4.21.1" } . . .
Instalar todas las dependencias necesarias para el proyecto:
santi@localhost:$ npm install
Actualizar las dependencias necesarias para el proyecto, si es necesario:
santi@localhost:$ npm update
También se pueden instalar una dependencia concreta y ésta será añadida automáticamente al fichero package.json como tal:
santi@localhost:$ npm install express
Backend
¿Qué es una API?
API sencilla sólo lectura
En un proyecto sencillo como este donde solamente vamos a desarrollar un backend sencillo, la estructura quedará algo asi:
- src/app.js
import express from 'express'; import cors from 'cors'; const app = express(); app.use(cors()); app.use(express.json()); const cities = { 'Zaragoza': { altitude: 199, population: 673010 }, 'Huesca': { altitude: 488, population: 53305 }, 'Teruel': { altitude: 915, population: 25900 } }; app.get('/cities', (req, res) => { return res.json(cities); }); app.get('/cities/:city', (req, res) => { const city = req.params.city; return res.json(cities[city]); }); app.listen(8080, () => { console.log('Iniciando el backend en el puerto 8080'); });
Asumiendo que tenemos un fichero package.json como el explicado anteriormente, podremos ejecutar la API con el siguiente comando:
santi@localhost:$ nodemon
Y tendremos los siguiente endpoints disponibles:
- http://localhost:8080/cities: Para ver el listado de todas las ciudades en formato JSON
- http://localhost:8080/cities/Zaragoza: Para ver los datos de una ciudad concreta (en este caso Zaragoza pero podríamos indicar cualquiera de las existentes)
También podemos utilizar aplicaciones como Postman que son específicas para desarrolladores. Son clientes diseñados para realizar llamadas a APIs y testear que éstas funcionan correctamente.
Frontend
Para el desarrollo de un frontend, desarrollaremos otra aplicación con su propio package.json y las dependencias que necesitaremos:
- package.json
{ "name": "cities-frontend", "version": "0.1", "source": "src/index.html", "type": "module", "scripts": { "start": "parcel --no-cache", "build": "parcel build" }, "devDependencies": { "buffer": "^6.0.3", "parcel": "latest", "process": "^0.11.10" }, "dependencies": { "axios": "^1.7.7" } }
Recordad instalar las dependencias antes de iniciar el proyecto:
santi@localhost:$ npm install
Y también de instalar parcel, que es otra dependencia un tanto particular que debe ser instalada como comando del sistema operativo (si estáis en linux es probable que tengáis que hacerlo como usuario root):
santi@localhost:$ npm install -g parcel
parcel es un comando que nos permitirá construir (build) el frontend muy automáticamente (HTML + JS) como una única aplicación. También, al arrancarlo con npm start, hará que la aplicación frontend quede escuchando automáticamente en el puerto 1234 (puerto por defecto).
Nuestro cliente estará basado, por el momento, en una única página HTML donde mostraremos los datos:
- index.html
<!DOCTYPE html> <html> <head> <title>Ejemplo 1: API con Node.js</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <script type="module" src="index.js"></script> </head> <body onload="readCities()"> <div class="container"> <h1>Ejemplo de API con Node.js</h1> <img src="nodejs.png"/> <ul id="cities" class="list-group"> </ul> </div> </body> </html>
Y este será nuestro código para llamar al backend, conseguir los datos y mostrarlos en el documento HTML anterior:
- index.js
import axios from 'axios'; function addCityNode(name) { const citiesUl = document.getElementById('cities'); const item = document.createElement('li'); item.className = 'list-group-item'; item.appendChild(document.createTextNode(name)); citiesUl.appendChild(item); }; window.readCities = function() { axios.get('http://localhost:8080/cities') .then((response) => { const cityList = response.data; Object.keys(cityList).forEach(cityName => { addCityNode(cityName); }); }); };
Tal y como hacemos con el backend, podremos ejecutar este frontend con el siguiente comando:
santi@localhost:$ npm start
Hay que tener en cuenta que backend y frontend deben escuchar en puertos diferentes para poder ejecutarse al mismo tiempo. En nuestro caso el backend escucha en el puerto 8080 y el frontend en el 1234 (es el puerto por defecto en el que escuchará porque estamos usando parcel para lanzarlo)
CRUD Completo Backend
GET /citiesDevuelve el listado de todas las ciudadesGET /cities/:cityDevuelve el detalle de una ciudadPOST /citiesRegistra una nueva ciudadDELETE /cities/:cityEliminar una ciudad (en caso de que la ciudad especificada no exista, se devuelve un mensaje de error)PUT /cities/:cityModifica los datos de una ciudad existente
import express from 'express'; import cors from 'cors'; const app = express(); app.use(cors()); app.use(express.json()); const cities = { 'Zaragoza': { altitude: 199, population: 673010 }, 'Huesca': { altitude: 488, population: 53305 }, 'Teruel': { altitude: 915, population: 25900 } }; app.get('/cities', (req, res) => { return res.status(200).json(cities); }); app.get('/cities/:city', (req, res) => { const city = req.params.city; return res.status(200).json(cities[city]); }); app.post('/cities', (req, res) => { const name = req.body.name; const altitude = req.body.altitude; const population = req.body.population; cities[name] = { altitude: altitude, population: population }; return res.status(201).json(cities[name]); }); app.delete('/cities/:city', (req, res) => { const name = req.params.city; if (cities[name] === undefined) { res.status(404).json({ error: 'No se ha podido encontrar la ciudad' }); } delete cities[name]; return res.status(204).end(); }); app.put('/cities/:city', (req, res) => { const name = req.params.city; const altitude = req.body.altitude; const population = req.body.population; cities[name] = { altitude: altitude, population: population }; return res.status(200).json(cities[name]); }); app.listen(8080, () => { console.log('Iniciando el backend en el puerto 8080'); });
Gestión de errores
La gestión de errores es una buena práctica que consiste en implementar/desarrollar controles para todas las posibles situaciones en las que nuestro código podría fallar de forma inesperada. Por ejemplo, alguien invoca nuestra operación de ver los detalles de una pelicula y se indica un título que no existe. ¿Qué debería pasar en ese caso?
En el ejemplo anterior, el CRUD completo, se incluye un caso de gestión de errores. Antes de eliminar una pelicula cuyo nombre se ha pasado como parámetro, se comprueba que exista. Asi, se puede notificar al consumidor (quien ha invocado la operación) que ha habido un error y no se ha podido localizar la pelicula si ésta no existe:
- Se response con un código de error
404 Not Foundque significa que el recurso solicitado no existe (la pelicula en este caso) - Se incluye un mensaje de error que proporciona información adicional que puede ayudar a diagnosticar el error o problema (en este caso es bastante sencillo pero en determinadas ocasiones puede ser más complicado saber qué ha pasado)
. . . if (cities[name] === undefined) { return res.status(404).json({ error: 'No se ha podido encontrar la ciudad' }); } . . .
Validación
La validación es una buena práctica que consiste en comprobar que los valores del los datos que nos envian quienes invocan nuestras operaciones son correctos y de acuerdo a lo esperado. Por ejemplo, algo invoca nuestra operación de registro de ciudades y envia un texto como valor para el campo population. ¿Qué debería pasar ahi? Si no controlamos casos como este, dependiendo de la complejidad del código, el backend podría fallar y detenerse o bien podrían darse casos no esperados (como que la población de una ciudad sea un texto sin sentido).
Casos como el comentado anteriormente pueden controlarse de esta manera:
. . . const population = req.body.population; if (typeof population !== 'number' || population <= 0) { return res.status(400).json({ error: 'population debe ser un número mayor que cero' }); } . . .
CRUD Backend + Frontend
Una vez que hemos visto una sencilla API con unicamente un par de operaciones de lectura, vamos a ver un ejemplo algo más completo. Es lo que también se conoce como CRUD que es el acrónimo que se utiliza para referirse a las 4 operaciones básica que permite que los usuarios puedan registrar (Create), leer (Read), modificar (Update) y borrar (Delete) información utilizando algún tipo de software.
En el caso de la API (backend) deberán ser diseñadas conforme a las convenciones que vimos un poco antes cuando explicamos lo que era una API.
En este caso necesitamos dos proyectos separados: backend y frontend. Asi, tendremos ambos lados de la aplicación (servidor y cliente) totalmente separados. La estructura del proyecto completo será algo asi:
Backend
Vamos a ampliar nuestro backendo con una nueva operación POST que permite añadir nuevas ciudades al listado que tenemos:
- backend/src/app.js
import express from 'express'; import cors from 'cors'; const app = express(); app.use(cors()); app.use(express.json()); const cities = { 'Zaragoza': { altitude: 199, population: 673010 }, 'Huesca': { altitude: 488, population: 53305 }, 'Teruel': { altitude: 915, population: 25900 } }; app.get('/cities', (req, res) => { return res.json(cities); }); app.get('/cities/:city', (req, res) => { const city = req.params.city; return res.json(cities[city]); }); app.post('/cities', (req, res) => { const name = req.body.name; const altitudeValue = req.body.altitude; const populationValue = req.body.population; cities[name] = { altitude: altitudeValue, population: populationValue }; return res.status(201).end(); }); app.listen(8080, () => { console.log('Iniciando el backend en el puerto 8080'); });
Frontend
Y en el lado frontend añadiremos un formulario que permita introducir los datos necesarios para luego añadir esa ciudad y sus detalles a la lista que tenemos:
- frontend/src/index.html
<!DOCTYPE html> <html> <head> <title>Ejemplo 1: API con Node.js</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <script type="module" src="index.js"></script> </head> <body onload="readCities()"> <div class="container"> <h1>Ejemplo de API con Node.js</h1> <img src="nodejs.png"/> <ul id="cities" class="list-group"> </ul> <hr/> <form> <div class="mb-3"> <label for="name" class="form-label">Nombre</label> <input class="form-control" type="text" id="name"/> </div> <div class="mb-3"> <label for="altitude" class="form-label">Altitud</label> <input class="form-control" type="text" id="altitude"/> </div> <div class="mb-3"> <label for="population" class="form-label">Población</label> <input class="form-control" type="text" id="population"/> </div> <button type="button" class="btn btn-primary" onClick="addCity()">Add city</button> </form> </div> </body> </html>
En la lógica del frontend añadimos lo necesario para recoger los datos del formulario e invocar al backend para “registrar” la ciudad con sus detalles:
- frontend/src/index.js
import axios from 'axios'; function addCityNode(name) { const citiesUl = document.getElementById('cities'); const item = document.createElement('li'); item.className = 'list-group-item'; item.appendChild(document.createTextNode(name)); const button = document.createElement('button'); button.className = 'btn-close' button.onclick = function() { removeCity(name); item.remove(); }; item.appendChild(button); citiesUl.appendChild(item); }; window.readCities = function() { axios.get('http://localhost:8080/cities') .then((response) => { const cityList = response.data; Object.keys(cityList).forEach(cityName => { addCityNode(cityName); }); }); }; window.addCity = function() { const name = document.getElementById('name').value; const altitude = document.getElementById('altitude').value; const population = document.getElementById('population').value; if (name === '') { alert('El nombre es un campo obligatorio'); return; } axios.post('http://localhost:8080/cities', { name: name, altitude: altitude, population: population }).then(() => { addCityNode(name); }); }; window.removeCity = function(name) { console.log(name + ' was removed'); // TODO Remove the city };
En el frontend veremos que también hemos añadido una X a modo de botón al lado de cada ciudad. Como ejercicio nos queda pendiente hacer que al pulsar en ese botón, la ciudad se elimine (también en el backend)
API con CRUD completo y base de datos
Backend: API
El primer paso será preparar el fichero de configuración de la base de datos, indicando el tipo (SQLite) y la ubicación (el fichero 'cities.db').
- src/configuration/database.js
import knex from 'knex'; // Configuración de la base de datos: tipo, ubicación y otros parámetros export const db = knex({ client: 'sqlite3', connection: { filename: 'cities.db' }, useNullAsDefault: true });
En el fichero principal simplemente añadiremos las operaciones definidas en el fichero 'route/cities.js' e iniciaremos el backend.
- src/app.js
import express from 'express'; import { citiesRouter } = from './route/cities.js'; const app = express(); app.use(express.json()); app.use('/', citiesRouter); app.listen(8080, () => { console.log('Iniciando el backend en el puerto 8080'); });
El fichero 'routes/cities.js' contiene la lista de todas las operaciones definidas en este backend y que tendremos que implementar correctamente en la capa controller:
- src/route/cities.js
import express from 'express'; const citiesRouter = express.Router(); import { getCities, getCity, postCity, putCity, deleteCity } from '../controller/cities.js'; citiesRouter.get('/cities', getCities); citiesRouter.get('/cities/:city', getCity); citiesRouter.post('/cities', postCity); citiesRouter.put('/cities/:city', putCity); citiesRouter.delete('/cities/:city', deleteCity); export { citiesRouter };
La capa controller implementa todas las operaciones que se han definido en la capa routes invocando a la capa service que será donde desarrollaremos toda la lógica. En esta capa controller simplemente leeremos la petición (que puede tener o no algún parámetro), validaremos dicha petición y sus parámetros e invocaremos a la capa service para ejecutar toda la lógica asociada a la misma:
- src/controller/cities.js
import { findAllCities, findCity, registerCity, modifyCity, removeCity} from '../service/cities.js'; // Operación que devuelve todas las ciudades de la base de datos const getCities = (async (req, res) => { const data = await findAllCities(); res.status(200).json(data); }); // Operación que devuelve una ciudad determinada const getCity = (async (req, res) => { const data = await findCity(req.params.city); res.status(200).json(data); }); // Operación que registra una nueva ciudad en la base de datos const postCity = (async (req, res) => { await registerCity(req.body.name, req.body.population, req.body.altitude); res.status(201).json({}); }); // Operación que modifica una ciudad en la base de datos const putCity = (async (req, res) => { await modifyCity(req.params.city, req.body.population, req.body.altitude); res.status(204).json({}); }); // Operación que elimina una ciudad de la base de datos const deleteCity = (async (req, res) => { await removeCity(req.params.city); res.status(204).json({}) }); export { getCities, getCity, postCity, putCity, deleteCity, };
En nuestro caso, en la capa service, será donde accedamos a la base de datos a por la información requerida, hagamos la operaciones de lógica necesarias (si las hay) y devolveremos la información a la capa anterior (controller) para que ésta la devuelva a su superior (que será quien haya realizado la petición, el frontend):
- src/service/cities.js
import { db } from '../configuration/database.js'; // Operación que devuelve todas las ciudades de la base de datos const findAllCities = (async () => { const result = await db('cities').select('*'); return result; }); // Operación que devuelve una ciudad determinada const findCity = (async (cityName) => { const result = await db('cities').select('*').where({name: cityName}).first(); return result; }); // Operación que registra una nueva ciudad en la base de datos const registerCity = (async (cityName, cityPopulation, cityAltitude) => { const result = await db('cities').insert({ name: cityName, population: cityPopulation, altitude: cityAltitude }); return result; }); // Operación que modifica una ciudad en la base de datos const modifyCity = (async (cityName, cityPopulation, cityAltitude) => { const result = await db('cities').where({ name: cityName }).update({ population: cityPopulation, altitude: cityAltitude }); return result; }); // Operación que elimina una ciudad de la base de datos const removeCity = (async (cityName) => { const result = await db('cities').del().where({name: cityName}); return result; }); export { findAllCities, findCity, registerCity, modifyCity, removeCity, };
Frontend: Utilidades
- documentUtils.js
const icons = new Map(); icons.set('edit', '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16">' + '<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325"/>' + '</svg>' ); icons.set('delete', '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">' + '<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/>' + '<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/>' + '</svg>' ); // TODO Añadir más iconos const el = function (elementId) { return document.getElementById(elementId); }; const icon = function(iconName) { return icons.get(iconName); }; const td = function(text) { return '<td>' + text + '</td>'; } module.exports = { el, icon, td };
Frontend: carga de datos en una tabla
- index.html
<!DOCTYPE html> <html> <head> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> <script type="module" src="index.js"></script> </head> <body onload="readCities()"> <div class="container"> <ul class="nav nav-tabs"> <li class="nav-item"> <a class="nav-link active" aria-current="page" href="index.html">Listado</a> </li> <li class="nav-item"> <a class="nav-link" href="registro.html">Registro</a> </li> </ul> <h1>Listado de ciudades</h1> <table class="table"> <thead> <tr> <th>Nombre</th> <th>Población</th> <th>Área</th> <th>. . .</th> </tr> </thead> <tbody id="tableBody"> </tbody> </table> </ul> </div> </body> </html>
- index.js
import axios from 'axios'; import { el, icon, td } from './documentUtil'; import { notifyOk } from './dialogUtil'; window.readCities = function() { axios.get('http://localhost:8080/cities') .then((response) => { const cityList = response.data; const cityTable = el('tableBody'); cityList.forEach(city => { const row = document.createElement('tr'); row.id = 'city-' + city.id; row.innerHTML = td(city.name) + td(city.population) + td(city.area) + '<a class="btn btn-warning" href="modify.html?id=' + city.id + '">' + icon('edit') + '</a> ' + '<a class="btn btn-danger" href="javascript:removeCity(' + city.id + ')">' + icon('delete') + '</a>'; cityTable.appendChild(row); }) }); }; window.removeCity = function(id) { if (confirm('¿Está seguro de que desea eliminar esta ciudad?')) { axios.delete('http://localhost:8080/cities/' + id) .then((response) => { if (response.status == 204) { notifyOk('Ciudad eliminada correctamente'); el('city-' + id).remove(); } }); } };
Frontend: Diálogos
- dialogUtils.js
import Toastify from 'toastify-js'; import 'toastify-js/src/toastify.css'; const notifyError = function(message) { Toastify({ text: message, duration: 3000, gravity: 'top', position: 'center', style: { background: "red", }, }).showToast(); }; const notifyOk = function(message) { Toastify({ text: message, duration: 3000, gravity: 'top', position: 'center', style: { background: "green", }, }).showToast(); }; module.exports = { notifyError, notifyOk };
Frontend: formulario de registro
- register.html
<!DOCTYPE html> <html> <head> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> <script type="module" src="register.js"></script> </head> <body> <div class="container"> <ul class="nav nav-tabs"> <li class="nav-item"> <a class="nav-link" aria-current="page" href="index.html">Listado</a> </li> <li class="nav-item"> <a class="nav-link active" href="register.html">Registro</a> </li> </ul> <h1>Registro ciudades</h1> <form action="http://localhost:8080/cities"> <div class="mb-3"> <label for="title" class="form-label">Nombre</label> <input class="form-control" type="text" id="name"/> </div> <div class="mb-3"> <label for="description" class="form-label">Población</label> <input class="form-control" type="text" id="population"/> </div> <div class="mb-3"> <label for="year" class="form-label">Area</label> <input class="form-control" type="text" id="area"/> </div> <button type="button" class="btn btn-primary" onClick="addCity()">Registrar</button> </form> </div> </body> </html>
- register.js
import axios from 'axios'; import { notifyError, notifyOk } from './dialogUtil.js'; import { el } from './documentUtil.js'; window.addCity = function() { const name = el('name').value; const population = el('population').value; const area = el('area').value; // TODO Validación de datos if (name === '') { notifyError('El nombre es un campo obligatorio'); return; } axios.post('http://localhost:8080/cities', { name: name, population: population, area: area }); // TODO Confirmar al usuario que todo ha ido bien (o mal) notifyOk('Ciudad registrada'); // TODO Limpiar el formulario el('name').value = ''; el('population').value = ''; el('area').value = ''; };
Funciones de utilidad
function getDays(fromDate, toDate) { const days = toDate.getTime() - fromDate.getTime(); return Math.abs(Math.round(days / (1000 * 3600 * 24))); } function getDaysFromNow(date) { const now = new Date(Date.now()); return getDays(date, now); } function getYearsFromNow(date) { const now = new Date(Date.now()); return Math.ceil(getDays(date, now) / 365); } module.exports = { getDaysFromNow, getYearsFromNow, getDays }
MariaDB como base de datos
const knex = require('knex'); . . . const { config } = require('../config/configuration'); // Configuración de la base de datos: tipo, ubicación y otros parámetros const db = knex({ client: 'mysql', connection: { host: config.db.host, port: config.db.port, user: config.db.user, password: config.db.password, database: config.db.database }, useNullAsDefault: true });
Ejercicios
- Implementa una API que ofrezca información sobre peliculas. De cada pelicula se guardará: titulo, argumento, fecha de estreno, categoria, duracion en minutos, número de sala, si se ofrece o no en IMAX y pais de origen. Deberá tener las siguientes operaciones:
- Listados de todas las peliculas con todos los detalles
- Detalle de cada pelicula (por el titulo)
- Registro de nuevas peliculas
- Baja de peliculas
- Actualización de las peliculas existentes
- Añade control de errores a tu API de peliculas:
- La operación que muestra el detalle de una pelicula devolverá un mensaje de error cuando la pelicula no exista
- La operación de registro devolverá un mensaje de error si ya exista una pelicula con el nombre de la que se quiere dar de alta
- La operación de baja de peliculas devolverá un mensaje de error si la pelicula no existe
- La operación de actualización devolverá un mensaje de error si la pelicula indicada no existe
- Controlar también las siguientes restricciones, devolviendo un mensaje de error cuando no se cumplan:
- La duración de una película es siempre un número mayor que cero
- El titulo y el argumento son campos obligatorios a la hora de registrar una pelicula
- El campo sala solamente podrá ser un valor entre 1 y 9 (que son las salas que tiene el cine)
- Prepara una colección Postman que permita probar todas las operaciones desarrolladas en tu API de peliculas. No olvides preparar requests para todos los casos posibles
- ¿Qué ocurre cuando, al registrar una película, se envian campos que no existen? ¿Se debería fallar? ¿Qué debería ocurrir con esa información de más que se envía?
- ¿Te has fijado en el patrón que sigue los códigos de error que has implementado en las operaciones del backend desarrollado? ¿Siguen alguna lógica? (Pista: https://datos.codeandcoke.com/apuntes:http)
- Imagina que algo falla, de forma inesperada, cuando se está ejecutando el código del backend. ¿Qué error se debería devolver? (Pista: https://datos.codeandcoke.com/apuntes:http)
- ¿Qué debería hacer el backend si alguien está invocando las operaciones de forma abusiva? (Pista: https://datos.codeandcoke.com/apuntes:http)
- Añade lo necesario para que también se puedan realizar operaciones similares pero con actores (de los que se almacene nombre, apellidos, pais de origen y si alguna vez ganaron un Oscar)
- ¿Cómo podríamos hacer que peliculas y actores estuvieran relacionadas? La idea sería saber qué actores han actuado en una determinada película, por ejemplo
Proyectos de ejemplo
Todos los proyectos de ejemplo de esta parte están en el repositorio nodejs de GitHub.
- cities-backend: API sencilla. Sólo backend. Sin base de datos
- cities-backend-db: API con CRUD completo. Sólo backend. Los datos están almacenados en una base de datos SQLite
- cities-backend-db-routes: API con CRUD completo. Sólo backend. Los datos están almacenados en una base de datos SQLite. El código está organizado por capas (route, controller, service)
- cities-backend-frontend: API sencilla. Incluye un frontend HTML + Javascript con llamadas al backend usando axios. Sin base de datos
- cities-backend-frontend-crud: API sencilla con operación de registro. Incluye un frontend HTML + Javascript con llamadas al backend usando axios. Sin base de datos
- cities_collection.json: Colección de requests para usar con Hoppscotch y cualquiera de los proyectos cities-backend
© 2024 Santiago Faci

