Entornos de desarrollo

1º DAM/DAW - Curso 2026-2027

User Tools

Site Tools


apuntes:backend_frontend

This is an old revision of the document!


Desarrollo de aplicaciones: backend + frontend

frontendbackend.jpg
Figure 1: Frontend vs Backend (Fuente: https://ecdisis.com/que-es-frontend-y-backend/)

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 fichero package.json de forma interactiva (si se añade –yes se rellenará directamente con valores por defecto)
  • npm run: Lista los scripts disponibles
  • npm run <nombre_script: Ejecuta el script cuyo nombre se para como parámetro
  • npm start: Ejecuta el script start (se puede ejecutar sin añadir run al 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?

Figure 2: Web API (Fuente: https://www.programmingwithmukesh.com/articles/web-api)
Figure 3: Tipos de operaciones en una Web 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:

Figure 4: Estructura proyecto cities-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) => {
    res.json(cities);
});
 
app.get('/cities/:city', (req, res) => {
    const city = req.params.city;
    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:

Figure 5: Listado de todas las ciudades
Figure 6: Detalles de una ciudad concreta

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.

Figure 7: Postman

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)

Figure 8: cities-frontend

CRUD Completo Backend

  • GET /cities Devuelve el listado de todas las ciudades
  • GET /cities/:city Devuelve el detalle de una ciudad
  • POST /cities Registra una nueva ciudad
  • DELETE /cities/:city Eliminar una ciudad (en caso de que la ciudad especificada no exista, se devuelve un mensaje de error)
  • PUT /cities/:city Modifica 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) => {
    res.status(200).json(cities);
});
 
app.get('/cities/:city', (req, res) => {
    const city = req.params.city;
    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
    };
 
    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];
 
    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
    };
 
    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 Found que 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) {
    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) {
    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:

Figure 9: Estructura proyecto backend + frontend

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) => {
    res.json(cities);
});
 
app.get('/cities/:city', (req, res) => {
    const city = req.params.city;
    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
    };
 
    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)

Figure 10: cities-frontend-crud

API con CRUD completo y base de datos

Figure 11: Estructura proyecto Node.js

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

  1. 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:
    1. Listados de todas las peliculas con todos los detalles
    2. Detalle de cada pelicula (por el titulo)
    3. Registro de nuevas peliculas
    4. Baja de peliculas
    5. Actualización de las peliculas existentes
  2. Añade control de errores a tu API de peliculas:
    1. La operación que muestra el detalle de una pelicula devolverá un mensaje de error cuando la pelicula no exista
    2. 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
    3. La operación de baja de peliculas devolverá un mensaje de error si la pelicula no existe
    4. La operación de actualización devolverá un mensaje de error si la pelicula indicada no existe
    5. Controlar también las siguientes restricciones, devolviendo un mensaje de error cuando no se cumplan:
      1. La duración de una película es siempre un número mayor que cero
      2. El titulo y el argumento son campos obligatorios a la hora de registrar una pelicula
      3. El campo sala solamente podrá ser un valor entre 1 y 9 (que son las salas que tiene el cine)
  3. 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

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

apuntes/backend_frontend.1790675464.txt.gz · Last modified: by Santiago Faci