Unidad 3: Funciones y Manejo de Errores

3.4 Ejemplo práctico con Async/Await

Este microservicio proporciona información sobre ciudades españolas almacenadas en un archivo JSON.

📌 Introducción a las API REST

Este microservicio sigue el concepto de API REST (Representational State Transfer), un estilo arquitectónico que permite la comunicación entre sistemas a través de HTTP.

Características de una API REST

  • Basado en recursos: Cada entidad (en este caso, ciudades) es un recurso accesible mediante una URL única.
  • Uso de métodos HTTP estándar: Se utilizan métodos como:
    • GET para obtener datos.
    • POST para enviar datos (no implementado en este ejemplo).
    • PUT para actualizar datos.
    • DELETE para eliminar recursos.
  • Formato de datos JSON: Los datos se envían y reciben en formato JSON, lo que facilita la interoperabilidad con otros sistemas.

Este microservicio REST proporciona información sobre ciudades españolas mediante dos endpoints:

  1. GET /ciudades → Devuelve la lista completa de ciudades.
  2. GET /ciudades/:codigo → Devuelve la información de una ciudad específica según su código.

1️⃣ Archivo JSON con los datos (ciudades.json)

ciudades.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[
    { "codigo": "MAD", "nombre": "Madrid", "poblacion": 3223000 },
    { "codigo": "BCN", "nombre": "Barcelona", "poblacion": 1620000 },
    { "codigo": "VAL", "nombre": "Valencia", "poblacion": 791000 },
    { "codigo": "SEV", "nombre": "Sevilla", "poblacion": 688000 },
    { "codigo": "ZAR", "nombre": "Zaragoza", "poblacion": 674000 },
    { "codigo": "MAL", "nombre": "Málaga", "poblacion": 574000 },
    { "codigo": "MUR", "nombre": "Murcia", "poblacion": 460000 },
    { "codigo": "PAL", "nombre": "Palma de Mallorca", "poblacion": 415000 },
    { "codigo": "BIL", "nombre": "Bilbao", "poblacion": 345000 },
    { "codigo": "VGO", "nombre": "Vigo", "poblacion": 296000 }
]

2️⃣ Servidor Node.js con Express y Async/Await (server.js)

server.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const express = require("express");
const fs = require("fs/promises"); // Uso de fs con promesas para async/await
const app = express();
const PORT = 3000;

app.use(express.json()); // Middleware para parsear JSON

// Función para leer el JSON de ciudades
async function cargarCiudades() {
    try {
        const data = await fs.readFile("ciudades.json", "utf-8");
        return JSON.parse(data);
    } catch (error) {
        console.error("Error al leer el archivo JSON", error);
        return [];
    }
}

// 📌 Endpoint para obtener todas las ciudades
app.get("/ciudades", async (req, res) => {
    const ciudades = await cargarCiudades();
    res.json(ciudades);
});

// 📌 Endpoint para obtener una ciudad por su código
app.get("/ciudades/:codigo", async (req, res) => {
    const codigo = req.params.codigo.toUpperCase();
    const ciudades = await cargarCiudades();
    const ciudad = ciudades.find(c => c.codigo === codigo);

    if (ciudad) {
        res.json(ciudad);
    } else {
        res.status(404).json({ error: "Ciudad no encontrada" });
    }
});

// Iniciar servidor
app.listen(PORT, () => {
    console.log(`Servidor ejecutándose en http://localhost:${PORT}`);
});

middleware express.json()

El middleware express.json() se utiliza para analizar los datos JSON enviados en las solicitudes HTTP. Esto permite acceder a los datos JSON en req.body en las rutas. Si no utilizamos el middleware express.json(), req.body será undefined al intentar acceder a los datos JSON. Por ejemplo el siguiente código no funcionará correctamente:

1
2
3
4
app.post("/ciudades", async (req, res) => {
    const { codigo, nombre, poblacion } = req.body;
    // ...
});
Dará error al intentar acceder a req.body porque no se ha analizado el JSON de la solicitud. La alternativa es bastante más compleja y tediosa:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
app.post("/ciudades", async (req, res) => {
    let data = "";
    req.on("data", chunk => {
        data += chunk;
    });
    req.on("end", () => {
        const { codigo, nombre, poblacion } = JSON.parse(data);
        // ...
    });
});
Por eso es recomendable utilizar el middleware express.json() para analizar automáticamente los datos JSON de las solicitudes.


3️⃣ Cómo Ejecutar el Microservicio

📌 1. Instalar Node.js y Express

Ejecuta los siguientes comandos:

npm init -y
npm install express

📌 2. Crear los archivos

  • Guarda el archivo ciudades.json con los datos.
  • Guarda el archivo server.js con el código del servidor.

📌 3. Iniciar el servidor

Ejecuta el siguiente comando:

node server.js


4️⃣ Pruebas con el Servidor

✔ Obtener todas las ciudades

📌 Ruta: http://localhost:3000/ciudades

📌 Ejemplo de respuesta:

[
    { "codigo": "MAD", "nombre": "Madrid", "poblacion": 3223000 },
    { "codigo": "BCN", "nombre": "Barcelona", "poblacion": 1620000 }
]

✔ Obtener una ciudad por código

📌 Ruta: http://localhost:3000/ciudades/MAD

📌 Ejemplo de respuesta:

{ "codigo": "MAD", "nombre": "Madrid", "poblacion": 3223000 }

❌ Ciudad no encontrada

📌 Ruta: http://localhost:3000/ciudades/XYZ

📌 Respuesta:

{ "error": "Ciudad no encontrada" }



5️⃣ Cómo acceder a los servicios REST

Existen varias formas de probar y consumir servicios REST. Aquí te mostramos tres métodos: desde un plugin de VSCode, desde un navegador web y con Postman.

✔ Opción 1: Usar una Extensión de VSCode (REST Client)

  1. Instalar la extensión REST Client en VSCode.
  2. Crear un archivo con extensión .http o .rest.
  3. Escribir la petición:
GET http://localhost:3000/ciudades
  1. Hacer clic en Send Request para ver la respuesta en JSON.

✔ Opción 2: Desde el navegador Chrome

Si solo necesitas probar las peticiones GET, puedes hacerlo desde el navegador:

  1. Abre Google Chrome.
  2. Escribe en la barra de direcciones:
    http://localhost:3000/ciudades
    
  3. Verás la respuesta en formato JSON en pantalla.
  4. Para buscar una ciudad específica:
    http://localhost:3000/ciudades/MAD
    

Esto mostrará la información de la ciudad de Madrid.

✔ Opción 3: Usar Postman para probar la API

  1. Descargar e instalar Postman desde postman.com.
  2. Abrir Postman y crear una nueva solicitud (New Request).
  3. Seleccionar el método GET e ingresar la URL:
    http://localhost:3000/ciudades
    
  4. Hacer clic en Send y ver la respuesta JSON.
  5. Para buscar una ciudad específica, cambia la URL:
    http://localhost:3000/ciudades/MAD
    
  6. Si deseas probar otros métodos como POST o PUT, puedes enviar datos JSON en el Body.

5️⃣ Rutas adicionales en el microservicio

A continuación, se añaden tres nuevas rutas para modificar, añadir y eliminar ciudades. Cada una de estas rutas requiere diferentes métodos HTTP: POST, PUT y DELETE.

📌 1. Modificar una ciudad (PUT)

Para modificar los datos de una ciudad ya existente, utilizamos el método PUT. Esta ruta recibirá un objeto JSON con los nuevos datos de la ciudad y actualizará los datos en el archivo.

Ruta PUT /ciudades/:codigo
  • Descripción: Modifica los datos de una ciudad.
  • Cuerpo de la solicitud: Un objeto JSON con el nuevo nombre y población de la ciudad.
  • Ejemplo de solicitud POST:

Ejemplo de la solicitud PUT:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
app.put("/ciudades/:codigo", async (req, res) => {
    const codigo = req.params.codigo.toUpperCase();
    const { nombre, poblacion } = req.body;
    const ciudades = await cargarCiudades();
    const ciudad = ciudades.find(c => c.codigo === codigo);

    if (ciudad) {
        ciudad.nombre = nombre || ciudad.nombre;
        ciudad.poblacion = poblacion || ciudad.poblacion;

        await fs.writeFile("ciudades.json", JSON.stringify(ciudades, null, 2));
        res.json({ mensaje: "Ciudad actualizada", ciudad });
    } else {
        res.status(404).json({ error: "Ciudad no encontrada" });
    }
});

Petición:

  • Cabecera: Content-Type: application/json (importante al enviar datos JSON).
  • Cuerpo: Un objeto JSON con los campos nombre y poblacion.

Ejemplo de datos JSON:

{
  "nombre": "Madrid Capital",
  "poblacion": 3500000
}

📌 2. Añadir una ciudad (POST)

Para añadir una nueva ciudad, utilizamos el método POST. Esta ruta recibirá un objeto JSON con los datos de la nueva ciudad.

Ruta POST /ciudades

Descripción: Añadir una nueva ciudad al archivo JSON. Cuerpo de la solicitud: Un objeto JSON con los datos de la nueva ciudad. Ejemplo de solicitud POST:

Ejemplo de la solicitud PUT:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
app.post("/ciudades", async (req, res) => {
    const { codigo, nombre, poblacion } = req.body;
    const ciudades = await cargarCiudades();

    if (ciudades.find(c => c.codigo === codigo)) {
        return res.status(400).json({ error: "Código de ciudad ya existe" });
    }

    const nuevaCiudad = { codigo, nombre, poblacion };
    ciudades.push(nuevaCiudad);

    await fs.writeFile("ciudades.json", JSON.stringify(ciudades, null, 2));
    res.json({ mensaje: "Ciudad añadida", ciudad: nuevaCiudad });
});

Petición

  • Cabecera: Content-Type: application/json.
  • Cuerpo: Un objeto JSON con los campos codigo, nombre, y poblacion.

Ejemplo de datos JSON:

{
"codigo": "VLL",
"nombre": "Valladolid",
"poblacion": 300000
}

Las respuestas del servidor incluirán un mensaje indicando si la ciudad se ha añadido correctamente o si ya existía un código de ciudad igual.

📌 3. Eliminar una ciudad (DELETE)

Finalmente, para eliminar una ciudad, utilizamos el método DELETE. Esta ruta elimina la ciudad identificada por su código del archivo JSON.

Ruta DELETE /ciudades/:codigo

Descripción: Elimina una ciudad del archivo JSON. Cuerpo de la solicitud: No requiere cuerpo, solo el código de la ciudad en la URL.

Ejemplo de solicitud DELETE:

Ejemplo de la solicitud DELETE:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
app.delete("/ciudades/:codigo", async (req, res) => {
    const codigo = req.params.codigo.toUpperCase();
    const ciudades = await cargarCiudades();
    const index = ciudades.findIndex(c => c.codigo === codigo);

    if (index === -1) {
        return res.status(404).json({ error: "Ciudad no encontrada" });
    }

    const ciudadEliminada = ciudades.splice(index, 1);

    await fs.writeFile("ciudades.json", JSON.stringify(ciudades, null, 2));
    res.json({ mensaje: "Ciudad eliminada", ciudad: ciudadEliminada });
});

Petición

Cabecera: No requiere cuerpo, pero la Content-Type es application/json para las respuestas. URL: Incluye el código de la ciudad en la URL para identificar qué ciudad eliminar.

DELETE /ciudades/MAD

6️⃣ Resumen Este microservicio proporciona las siguientes operaciones sobre el recurso ciudades:

Método HTTP Ruta Descripción
GET /ciudades Obtiene todas las ciudades
GET /ciudades/:codigo Obtiene una ciudad por su código
PUT /ciudades/:codigo Modifica los datos de una ciudad
POST /ciudades Añade una nueva ciudad
DELETE /ciudades/:codigo Elimina una ciudad

Conclusión

Este microservicio muestra cómo utilizar async/await en Node.js para manejar archivos JSON de manera asíncrona y proporcionar datos a través de una API sencilla con Express.js.

Ejercicio

Dado el siguiente archivo JSON de datos, con ciudades y de cada ciudad sus aeropuertos:

[
    {
        "codigo": "MAD",
        "nombre": "Madrid",
        "poblacion": 3223000,
        "aeropuertos": [
            { "codigo": "MAD", "nombre": "Barajas" },
            { "codigo": "TOJ", "nombre": "Torrejón" }
        ]
    },
    {
        "codigo": "BCN",
        "nombre": "Barcelona",
        "poblacion": 1620000,
        "aeropuertos": [
            { "codigo": "BCN", "nombre": "El Prat" }
        ]
    }
]

Crea un microservicio que permita realizar las siguientes operaciones:

  1. Obtener todos los aeropuertos de una ciudad: GET /ciudades/:codigo/aeropuertos
  2. Añadir una ciudad: POST /ciudades/:codigo/
  3. Añadir un aeropuerto a una ciudad: POST /ciudades/:codigo/aeropuertos
  4. Eliminar un aeropuerto de una ciudad: DELETE /ciudades/:codigo/aeropuertos/:codigo_aeropuerto
  5. Dado el código de un aeropuerto, obtener la ciudad a la que pertenece: GET /aeropuertos/:codigo

Todas las operaciones deben ser asíncronas y utilizar async/await para manejar la lectura y escritura de archivos JSON.

Todas las operaciones deben devolver los datos en formato JSON y manejar los errores adecuadamente, devolviendo un código de estado 404 si no se encuentra la ciudad o el aeropuerto correspondiente.

Crear un fichero test.rest con las peticiones para probar cada una de las operaciones.

Ejercicio
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
const express = require("express");
const fs = require("fs/promises");
const app = express();
const PORT = 3000;

app.use(express.json());

async function cargarCiudades() {
    try {
        const
        data = await fs.readFile("ciudades.json", "utf-8");
        return JSON.parse(data);
    } catch (error) {     
        console.error("Error al leer el archivo JSON", error);
        return [];
    }
}

/**
 * Obtiene todos los aeropuertos de una ciudad
 */

app.get("/ciudades/:codigo/aeropuertos", async (req, res) => {
    const codigo = req.params.codigo.toUpperCase();
    const ciudades = await cargarCiudades();
    const ciudad = ciudades.find(c => c.codigo === codigo);

    if (ciudad) {
        res.json(ciudad.aeropuertos);
    } else {
        res.status(404).json({ error: "Ciudad no encontrada" });
    }
});

/**
 * Añade una ciudad
 */

app.post("/ciudades/:codigo", async (req, res) => {
    const codigo = req.params.codigo.toUpperCase();
    const { nombre, poblacion } = req.body;
    const ciudades = await cargarCiudades();
    const ciudad = ciudades.find(c => c.codigo === codigo);

    if (!ciudad) {
        ciudad.nombre = nombre || ciudad.nombre;
        ciudad.poblacion = poblacion || ciudad.poblacion;
        ciudad.aeropuertos = [];
        await fs.writeFile("ciudades.json", JSON.stringify(ciudades, null, 2));
        res.json({ mensaje: "Ciudad añadida", ciudad });
    } else {
        res.status(404).json({ error: "Código de ciudad repetido" });
    }
});

/**
 * Añade un aeropuerto a una ciudad
 */

app.post("/ciudades/:codigo/aeropuertos", async (req, res) => {
    const codigo = req.params.codigo.toUpperCase();
    const { codigo_aeropuerto, nombre } = req.body;
    const ciudades = await cargarCiudades();
    const ciudad = ciudades.find(c => c.codigo === codigo);

    if (ciudad) {
        ciudad.aeropuertos.push({ codigo: codigo_aeropuerto, nombre });
        await fs.writeFile("ciudades.json", JSON.stringify(ciudades, null, 2));
        res.json({ mensaje: "Aeropuerto añadido", aeropuerto: { codigo: codigo_aeropuerto, nombre } });
    } else {
        res.status(404).json({ error: "Ciudad no encontrada" });
    }
});

/**
 * Elimina un aeropuerto de una ciudad
 */

app.delete("/ciudades/:codigo/aeropuertos/:codigo_aeropuerto", async (req, res) => {
    const codigo = req.params.codigo.toUpperCase();
    const codigo_aeropuerto = req.params.codigo_aeropuerto.toUpperCase();
    const ciudades = await cargarCiudades();
    const ciudad = ciudades.find(c => c.codigo === codigo);

    if (ciudad) {
        const index = ciudad.aeropuertos.findIndex(a => a.codigo === codigo_aeropuerto);

        if (index !== -1) {
            const aeropuertoEliminado = ciudad.aeropuertos.splice(index, 1);
            await fs.writeFile("ciudades.json", JSON.stringify(ciudades, null, 2));
            res.json({ mensaje: "Aeropuerto eliminado", aeropuerto: aeropuertoEliminado });
        } else {
            res.status(404).json({ error: "Aeropuerto no encontrado" });
        }
    } else {
        res.status(404).json({ error: "Ciudad no encontrada" });
    }
});

/**
 * Obtiene la ciudad a la que pertenece un aeropuerto
 */

app.get("/aeropuertos/:codigo", async (req, res) => {
    const codigo = req.params.codigo.toUpperCase();
    const ciudades = await cargarCiudades();
    const ciudad = ciudades.find(c => c.aeropuertos.some(a => a.codigo === codigo));

    if (ciudad) {
        const aeropuerto = ciudad.aeropuertos.find(a => a.codigo === codigo);
        res.json({ ciudad: ciudad.nombre, aeropuerto });
    } else {
        res.status(404).json({ error: "Aeropuerto no encontrado" });
    }
});

// Iniciar servidor

app.listen(PORT, () => {
    console.log(`Servidor ejecutándose en http://localhost:${PORT}`);
});
GET http://localhost:3000/ciudades/MAD/aeropuertos
POST http://localhost:3000/ciudades/MAD/aeropuertos
{
"codigo_aeropuerto": "T4",
"nombre": "Terminal 4"
}
POST http://localhost:3000/ciudades/MAD/aeropuertos
{
"codigo": "MAD",
"nombre": "Barajas"
}
DELETE http://localhost:3000/ciudades/MAD/aeropuertos/T4
GET http://localhost:3000/aeropuertos/T4