La aplicación de Escena Viva tiene ahora una estructura sólida pero una sola ruta. En esta lección la llenamos: recuperamos los cuatro endpoints de la API del módulo 4 (/salud, /eventos, /eventos/:id, /eventos/:id/sesiones), añadimos las sesiones y los pedidos, y los organizamos en routers modulares con controladores separados. Por el camino compararemos cada mecanismo de Express con el enrutador.js que escribiste a mano, porque la mejor forma de entender app.get('/eventos/:id') es recordar lo que costaba hacerlo sin framework.
Contenido
- Métodos de ruta y anatomía de un manejador
- Parámetros de ruta y
req.params - Parámetros opcionales, comodines y expresiones regulares
- Los otros datos de la petición:
query,body,headers,ip express.Router(): routers modulares- Reorganizar la API de Escena Viva
- Controladores separados
router.route()yrouter.param()- El orden importa
- Los métodos de
res
- Métodos de ruta y anatomía de un manejador
Express expone un método por cada verbo HTTP, más all para todos:
aplicacion.get('/eventos', manejador); // leer una coleccion
aplicacion.post('/pedidos', manejador); // crear un recurso
aplicacion.put('/sesiones/:id', manejador); // reemplazar por completo
aplicacion.patch('/sesiones/:id', manejador); // modificar parcialmente
aplicacion.delete('/pedidos/:id', manejador); // eliminar
aplicacion.all('/api/*resto', manejador); // cualquier metodoCada uno registra una entrada en la pila interna de Express: un patrón, un método y una o varias funciones. Es literalmente lo que hacía tu enrutador.registrar(metodo, patron, manejador), con la diferencia de que Express admite varias funciones por ruta: en aplicacion.post('/pedidos', validarCuerpo, comprobarAforo, crearPedido) las tres se ejecutan en orden y cada una decide si continúa. Eso ya es middleware, y es el tema de 06-04.
Anatomía de un manejador
// peticion: IncomingMessage extendido; respuesta: ServerResponse extendido.
function manejador(peticion, respuesta, siguiente) {
// Tres salidas posibles:
// 1. Responder: respuesta.json(...) / respuesta.send(...) — la cadena termina.
// 2. Continuar: siguiente() — pasa al siguiente manejador que encaje.
// 3. Fallar: siguiente(error) — salta al manejador de errores (06-07).
respuesta.json({ estado: 'ok' });
}Una regla de oro: cada camino de ejecución debe terminar en exactamente una de esas tres cosas. Si un if no responde ni llama a siguiente(), la petición se queda colgada hasta que el cliente se rinde. Lo diagnosticaremos en 06-04.
- Parámetros de ruta y
req.params
req.paramsaplicacion.get('/eventos/:id', (peticion, respuesta) => {
respuesta.json({ idSolicitado: peticion.params.id });
});
// curl -s localhost:3000/eventos/evt-002 -> {"idSolicitado":"evt-002"}Compáralo con lo que hacía tu módulo 4. compilarPatron('/eventos/:id') recorría el patrón, sustituía cada :nombre por un grupo de captura, guardaba los nombres en un array y construía la regex /^\/eventos\/([^/]+)$/; después emparejar la ejecutaba sobre la ruta y reconstruía a mano el objeto { id: 'evt-002' }. Express hace lo mismo, con diferencias que importan:
| Aspecto | Tu compilarPatron |
Express (path-to-regexp) |
|---|---|---|
| Descodificación de la URL | Llamabas a decodeURIComponent tú |
Automática: /eventos/Sala%20B%C3%B3veda llega descodificado |
| Caracteres especiales y casos límite | Escapado a mano; riesgo de regex catastrófica | Escapado seguro y patrones acotados y probados |
| Múltiples parámetros | Funcionaba, pero el código crecía | /eventos/:idEvento/sesiones/:idSesion sin coste |
Varios parámetros en una ruta:
aplicacion.get('/eventos/:idEvento/sesiones/:idSesion', (peticion, respuesta) => {
const { idEvento, idSesion } = peticion.params;
respuesta.json({ idEvento, idSesion });
});
// GET /eventos/evt-001/sesiones/ses-001-2
// -> {"idEvento":"evt-001","idSesion":"ses-001-2"}Importante:
req.paramssiempre contiene cadenas. Si esperas un número, conviértelo y valídalo. En 06-06 lo haremos con esquemas en vez de a mano.
- Parámetros opcionales, comodines y expresiones regulares
Aquí es donde Express 5 rompió con Express 4; presta atención si te encuentras código antiguo.
| Necesidad | Express 4 | Express 5 |
|---|---|---|
| Parámetro opcional | /eventos/:id? |
/eventos{/:id} |
| Comodín | /archivos/* → req.params[0] |
Anónimo no permitido (lanza error); con nombre, /archivos/*ruta → req.params.ruta |
| Segmento repetible | /:seccion+ |
/*seccion (captura el resto) |
// Parametro opcional: responde a /eventos y a /eventos/evt-001.
aplicacion.get('/eventos{/:id}', (peticion, respuesta) => {
const { id } = peticion.params;
respuesta.json(id === undefined ? { tipo: 'coleccion' } : { tipo: 'elemento', id });
});
// Comodin con nombre: GET /descargas/informes/2026/ocupacion.csv
// -> peticion.params.ruta === 'informes/2026/ocupacion.csv'
aplicacion.get('/descargas/*ruta', (peticion, respuesta, siguiente) => {
try {
// El comodin es la puerta clasica al recorrido de directorios:
// reutilizamos resolverDentroDe del modulo 3.
respuesta.sendFile(resolverDentroDe(DIRECTORIO_INFORMES, peticion.params.ruta));
} catch (error) {
siguiente(error); // codigo RUTA_NO_PERMITIDA -> 403
}
});Fíjate en la lección que se repite: el comodín es cómodo y peligroso. Que Express extraiga la ruta por ti no significa que sea segura; resolverDentroDe sigue siendo imprescindible.
Para casos que la sintaxis de segmentos no cubre, se admite una expresión regular como patrón; con ella, las capturas llegan por índice numérico: aplicacion.get(/^\/eventos\/(evt-\d{3})$/, (p, r) => r.json({ id: p.params[0] })). Se puede, pero úsalo con moderación: una regex en la ruta es difícil de leer y esconde una validación donde nadie la busca. Es mejor aceptar :id y validar el formato con un esquema (06-06), porque así el error que devuelves es explicativo en vez de un 404 mudo.
- Los otros datos de la petición
| Propiedad | Contenido | Notas |
|---|---|---|
req.params |
Parámetros de ruta | Siempre cadenas, ya descodificadas |
req.query |
Query string interpretada | Solo lectura en Express 5 |
req.body |
Cuerpo interpretado | undefined sin express.json() (06-04) |
req.headers / req.get(n) |
Cabeceras en minúsculas / una cabecera | req.get('Content-Type') ignora mayúsculas |
req.ip, req.protocol, req.secure |
IP y esquema del cliente | Dependen de trust proxy (06-02) |
req.method, req.path, req.originalUrl |
Método y ruta | originalUrl conserva la URL completa antes de montajes |
// GET /api/eventos?sala=Sala%20Boveda&pagina=2&ordenar=fecha
aplicacion.get('/api/eventos', (peticion, respuesta) => {
const { sala, pagina = '1', ordenar = 'fecha' } = peticion.query;
respuesta.json({ sala, pagina: Number.parseInt(pagina, 10), ordenar });
});El error clásico al migrar a Express 5
En Express 4, req.query era una propiedad normal y mucho código "normalizaba" la query reasignándola:
// FUNCIONABA en Express 4 y FALLA en Express 5.
peticion.query = normalizar(peticion.query); // TypeError: Cannot set property query
// Correcto en Express 5: el resultado normalizado va a otra propiedad.
peticion.consulta = normalizar(peticion.query);En Express 5, req.query es un getter que interpreta la query string bajo demanda y la memoriza; no tiene setter. Ese es exactamente el patrón que usará el middleware validar() de 06-06 con req.datosValidados.
express.Router(): routers modulares
express.Router(): routers modularesUn Router es una mini-aplicación: tiene sus rutas y su middleware, pero no escucha en ningún puerto. Se monta en la aplicación (o en otro router) bajo un prefijo.
const rutasEventos = express.Router();
rutasEventos.get('/', listarEventos); // ruta relativa
rutasEventos.get('/:id', obtenerEvento); // ruta relativa
// El prefijo se decide al montar, no al declarar.
aplicacion.use('/api/eventos', rutasEventos);
// Resultado: GET /api/eventos y GET /api/eventos/:idLas ventajas frente a declarar todo en app.js: rutas relativas (el router no sabe dónde está montado, así que cambiar /api/eventos por /api/v2/eventos es una línea), middleware acotado (rutasPedidos.use(limitador) afecta solo a los pedidos), ficheros pequeños (un router por recurso, en vez de un app.js interminable) y reutilización (el mismo router se puede montar dos veces bajo prefijos distintos).
- Reorganizar la API de Escena Viva
// src/rutas/index.js — punto unico de montaje.
const express = require('express');
const { rutasEventos } = require('./eventos.js');
const { rutasSesiones } = require('./sesiones.js');
const { rutasPedidos } = require('./pedidos.js');
function crearRutasApi() {
const api = express.Router();
api.get('/salud', (p, r) => r.json({ estado: 'ok', instante: new Date().toISOString() }));
api.use('/eventos', rutasEventos);
api.use('/sesiones', rutasSesiones);
api.use('/pedidos', rutasPedidos);
return api;
}
module.exports = { crearRutasApi };// src/rutas/eventos.js
const express = require('express');
const cc = require('../controladores/eventos.js');
const rutasEventos = express.Router();
// Se ejecuta una sola vez por peticion que traiga :id, antes del manejador.
rutasEventos.param('id', cc.cargarEvento);
rutasEventos.get('/', cc.listarEventos);
rutasEventos.get('/:id', cc.obtenerEvento);
rutasEventos.get('/:id/sesiones', cc.listarSesionesDelEvento);
module.exports = { rutasEventos };
// src/rutas/sesiones.js y src/rutas/pedidos.js siguen el mismo patron.
rutasSesiones.get('/:idSesion', obtenerSesion);
rutasSesiones.get('/:idSesion/precios', obtenerPreciosDeSesion);
rutasPedidos.route('/').post(crearPedido);
rutasPedidos.route('/:idPedido').get(obtenerPedido);
// Y el montaje en src/app.js:
aplicacion.use(express.json({ limit: configuracion.limiteCuerpo }));
aplicacion.use('/api', crearRutasApi());
aplicacion.use(express.static(configuracion.directorioPublico, { index: 'index.html' }));Toda la API cuelga de /api y el front-end estático de publico/ se sirve en la raíz. Un solo use decide el versionado de la API entera.
- Controladores separados
El controlador traduce HTTP a dominio. No contiene reglas de negocio: llama al dominio y decide el código de estado.
// src/controladores/eventos.js
const { obtenerCatalogo, obtenerEventoPorId } = require('../catalogo-datos.js');
/** GET /api/eventos — listado resumido del catalogo. */
async function listarEventos(peticion, respuesta) {
const eventos = (await obtenerCatalogo()).map((e) => ({
id: e.id, titulo: e.titulo, sala: e.sala, numeroSesiones: e.numeroSesiones,
aforoTotal: e.aforoTotal, entradasVendidas: e.entradasVendidas, agotado: e.agotado,
}));
respuesta.json({ total: eventos.length, eventos });
}
// Middleware de router.param: carga el evento una sola vez. Si no existe,
// propaga el error de dominio (EVENTO_NO_ENCONTRADO -> 404).
async function cargarEvento(peticion, respuesta, siguiente, valorId) {
peticion.evento = await obtenerEventoPorId(valorId);
siguiente();
}
/** GET /api/eventos/:id — el evento ya viene cargado por cargarEvento. */
const obtenerEvento = (peticion, respuesta) => respuesta.json(peticion.evento.toJSON());
/** GET /api/eventos/:id/sesiones */
function listarSesionesDelEvento(peticion, respuesta) {
const { id, numeroSesiones, sesiones } = peticion.evento;
respuesta.json({
idEvento: id, total: numeroSesiones,
sesiones: sesiones.map((s) => ({
id: s.id, inicio: s.inicio, aforo: s.aforo, libres: s.libres,
agotada: s.agotada, precioCentimos: s.precioCentimos,
})),
});
}
module.exports = { listarEventos, cargarEvento, obtenerEvento, listarSesionesDelEvento };Tres cosas que no verás en este fichero, y es deliberado: no hay try/catch (en Express 5, si obtenerEventoPorId rechaza con código EVENTO_NO_ENCONTRADO, ese rechazo llega solo al manejador de errores, que lo traducirá a 404 con la tabla ESTADO_POR_CODIGO del módulo 4; ver 06-07); no hay lectura de ficheros, que es de catalogo-datos.js; y no hay reglas de aforo, que son de dominio/.
// src/controladores/pedidos.js — POST /api/pedidos
async function crearPedido(peticion, respuesta) {
// req.body existe gracias a express.json() (06-04);
// en 06-06 lo sustituiremos por req.datosValidados.
const pedido = await registrarPedido(peticion.body);
respuesta.status(201).location(`/api/pedidos/${pedido.id}`).json(pedido.toJSON());
}
router.route() y router.param()
router.route() y router.param()router.route()
Encadena varios métodos sobre la misma ruta sin repetir el patrón: rutasSesiones.route('/:idSesion').get(obtenerSesion).patch(actualizarSesion).delete(cancelarSesion).
Ventajas: el patrón se escribe una vez (menos riesgo de que una copia se desincronice) y queda visualmente claro qué operaciones admite el recurso. Además, Express genera automáticamente el 405 Method Not Allowed con la cabecera Allow correcta para los métodos no registrados de esa ruta — exactamente lo que tú programaste a mano en enrutador.js.
router.param()
rutasEventos.param('id', cargarEvento) registra un middleware que se ejecuta una vez por petición cuando la ruta contiene ese parámetro, antes de cualquier manejador. Con ello, las tres rutas /, /:id y /:id/sesiones comparten la carga del evento sin repetir una línea, y los controladores reciben req.evento ya resuelto.
| A favor | En contra |
|---|---|
| Elimina duplicación en rutas que comparten parámetro y deja los controladores muy limpios | El controlador depende de algo que no se ve en su fichero |
| Punto único para el 404 del recurso | Se ejecuta también donde quizá no lo necesitas, y confunde si dos routers usan el mismo nombre con semánticas distintas |
Cuidado importante: router.param() es local al router donde se registra. Si montas rutasSesiones en otro sitio, su param no se hereda. Y si el mismo nombre (:id) significa cosas distintas en dos routers, usa nombres distintos (:idEvento, :idSesion) como hemos hecho aquí.
- El orden importa
Express recorre su pila en el orden de registro y se queda con la primera entrada que encaje en método y ruta. Esto explica el 90 % de los "mi ruta no se ejecuta".
// MAL: el comodin se declara primero y se come todo lo demas.
rutasEventos.get('/*resto', servirPaginaGenerica);
rutasEventos.get('/destacados', listarDestacados); // NUNCA se ejecuta
rutasEventos.get('/:id', obtenerEvento); // NUNCA se ejecuta
// BIEN: de lo mas especifico a lo mas generico.
rutasEventos.get('/destacados', listarDestacados); // literal
rutasEventos.get('/:id', obtenerEvento); // parametro
rutasEventos.get('/*resto', servirPaginaGenerica); // comodinCon el orden malo, una petición a /api/eventos/destacados encaja con /*resto, que responde, y la cadena termina ahí: las otras dos rutas son código muerto. La regla práctica, en orden de declaración: primero las rutas literales (/destacados, /salud), después las que llevan parámetro (/:id), luego las de comodín (/*resto) y al final el 404. Es el mismo problema que ya tenías en el módulo 4: tu enrutador.js también recorría el array de rutas en orden. Express no lo resuelve por ti; hereda la misma regla.
La ruta 404 final
// Al final de src/app.js, despues de todas las rutas y estaticos.
aplicacion.use((peticion, respuesta, siguiente) => {
const mensaje = `No existe la ruta ${peticion.method} ${peticion.originalUrl}`;
siguiente(Object.assign(new Error(mensaje), { codigo: 'RUTA_NO_ENCONTRADA' }));
});Es un use sin ruta, así que encaja con todo lo que haya llegado hasta ahí sin ser atendido. No responde: propaga un error, para que el manejador central de 06-07 dé la respuesta en el formato { error: { codigo, mensaje, estado } } como cualquier otro error.
- Los métodos de
res
res| Método | Qué hace | Qué añade sobre node:http |
|---|---|---|
res.json(objeto) |
Serializa y envía JSON | Fija Content-Type y Content-Length, aplica json spaces, calcula ETag |
res.send(valor) |
Envía cadena, Buffer u objeto | Deduce el Content-Type según el tipo del argumento |
res.status(codigo) |
Fija el estado | Devuelve res, así que se encadena |
res.sendStatus(codigo) |
Estado + cuerpo con el texto del estado | res.sendStatus(204) responde en una línea |
res.location(url) |
Fija la cabecera Location |
Codifica la URL correctamente |
res.redirect([codigo], url) |
Location + estado + cuerpo |
Por defecto 302; usa 301 o 308 para permanentes |
res.sendFile(rutaAbsoluta) |
Envía un fichero | ETag, Content-Type, rangos y streaming: es tu estaticos.js |
res.set(n, v) / res.type(t) |
Cabeceras y Content-Type |
set admite un objeto; type acepta atajos ('json') |
res.end() |
Termina la respuesta | El mismo de node:http, sin extras |
// Ejemplos concretos en Escena Viva.
respuesta.status(201).location(`/api/pedidos/${pedido.id}`).json(pedido.toJSON());
respuesta.sendStatus(204); // cancelacion aceptada, sin cuerpo
// Redireccion permanente de una URL antigua.
aplicacion.get('/eventos/:id', (p, r) => r.redirect(301, `/api/eventos/${p.params.id}`));
// Varias cabeceras de una vez.
respuesta.set({ 'Cache-Control': 'public, max-age=60', 'X-Sala': 'Teatro Almendra' });Comprueba la equivalencia con el módulo 4: curl -si localhost:3000/api/eventos/evt-001 devuelve 200 OK con Content-Type: application/json; charset=utf-8, Content-Length y un ETag: W/"19c-...". Ese ETag lo calculaba tu estaticos.js a mano con un hash; res.json() lo hace por defecto para cualquier respuesta.
Errores Comunes y Consejos
- Declarar
/:idantes que/destacados. El parámetro captura la palabra literal y la ruta específica queda muerta. De lo específico a lo genérico, siempre. - Usar
*sin nombre en Express 5. Ya no está permitido: la aplicación falla al arrancar. Migra a*nombre. - Reasignar
req.query.TypeErroren Express 5; guarda el resultado normalizado en otra propiedad. - Esperar que
req.bodyexista sinexpress.json(). Seráundefinedy obtendrás unTypeError: el fallo más común al escribir el primerPOST. - Meter lógica de negocio en el controlador. Si calcula ocupaciones o valida aforos, esa lógica no se puede reutilizar desde los informes ni probar sin HTTP.
- Olvidar que
router.param()es local al router: no se hereda al montar en otro sitio. - Confiar en un
:idsin validar. Llega como cadena y puede ser cualquier cosa. Un 404 mudo no ayuda al cliente; en 06-06 devolveremos un 400 explicativo. - Consejo: en desarrollo, imprime la tabla de rutas al arrancar recorriendo
aplicacion.router.stack. Ver el orden real de registro cura muchos misterios.
Ejercicios
Ejercicio 1: filtrado y paginación por query
Amplía GET /api/eventos para que admita ?sala=, ?agotado=true|false, ?pagina= y ?porPagina= (por defecto 1 y 10, máximo 50). Devuelve { pagina, porPagina, total, totalPaginas, eventos }. No reasignes req.query. Con los datos semilla, ?porPagina=2 debe devolver 2 páginas para los 3 eventos.
Ejercicio 2: router de sesiones con route() y param()
Crea src/rutas/sesiones.js con un router.param('idSesion', cargarSesion) que busque la sesión recorriendo el catálogo y la deje en req.sesion (propagando SESION_NO_ENCONTRADA si no existe), y expón con router.route('/:idSesion') un GET que devuelva el detalle. Comprueba que PUT /api/sesiones/ses-001-1 devuelve 405 con la cabecera Allow.
Ejercicio 3: el orden que rompe
Escribe una aplicación con tres rutas mal ordenadas (/*resto, /destacados, /:id), demuestra con curl que solo se ejecuta la primera, reordénalas y vuelve a probar. Añade además un endpoint /depuracion/rutas que devuelva la lista de rutas registradas en su orden real.
Soluciones
Solución 1
// src/controladores/eventos.js (fragmento)
const MAXIMO_POR_PAGINA = 50;
async function listarEventos(peticion, respuesta) {
// Lectura sin reasignar req.query; toda la conversion es manual.
const { sala, agotado } = peticion.query;
const pagina = Math.max(1, Number.parseInt(peticion.query.pagina ?? '1', 10) || 1);
const bruto = Number.parseInt(peticion.query.porPagina ?? '10', 10) || 10;
const porPagina = Math.min(MAXIMO_POR_PAGINA, Math.max(1, bruto));
let eventos = await obtenerCatalogo();
if (sala) eventos = eventos.filter((e) => e.sala === sala);
if (agotado === 'true' || agotado === 'false') {
eventos = eventos.filter((e) => e.agotado === (agotado === 'true'));
}
const total = eventos.length;
const desde = (pagina - 1) * porPagina;
respuesta.json({
pagina, porPagina, total,
totalPaginas: Math.max(1, Math.ceil(total / porPagina)),
eventos: eventos.slice(desde, desde + porPagina).map((e) => e.toJSON()),
});
}Toda esta conversión manual —parseInt, topes, ?? '1'— es justo lo que sustituiremos por un esquema en 06-06.
Solución 2
// src/controladores/sesiones.js
async function cargarSesion(peticion, respuesta, siguiente, valorId) {
const catalogo = await obtenerCatalogo();
for (const evento of catalogo) {
const sesion = evento.buscarSesion(valorId);
if (sesion) {
peticion.sesion = sesion;
peticion.eventoDeLaSesion = evento;
siguiente();
return;
}
}
const mensaje = `No existe la sesion ${valorId}`;
siguiente(Object.assign(new Error(mensaje), { codigo: 'SESION_NO_ENCONTRADA' }));
}
const obtenerSesion = (peticion, respuesta) =>
respuesta.json({ ...peticion.sesion, idEvento: peticion.eventoDeLaSesion.id });
module.exports = { cargarSesion, obtenerSesion };Y curl -si -X PUT localhost:3000/api/sesiones/ses-001-1 devuelve 405 Method Not Allowed con Allow: GET, HEAD, generado por Express sin que tú escribas nada.
Solución 3
// orden.js — version que falla; reordena para comparar.
const aplicacion = require('express')();
aplicacion.get('/*resto', (p, r) => r.json({ ruta: 'comodin' }));
aplicacion.get('/destacados', (p, r) => r.json({ ruta: 'destacados' }));
aplicacion.get('/:id', (p, r) => r.json({ ruta: 'parametro' }));
// La lista de rutas en su orden real se saca de la pila interna.
aplicacion.get('/depuracion/rutas', (peticion, respuesta) => {
respuesta.json(
aplicacion.router.stack.filter((c) => c.route).map((c) => c.route.path)
);
});
aplicacion.listen(3000);Con ese orden, curl -s localhost:3000/destacados y curl -s localhost:3000/evt-001 devuelven los dos {"ruta":"comodin"}. Tras reordenar (destacados, :id, *resto) devuelven {"ruta":"destacados"} y {"ruta":"parametro"}. Nota: /depuracion/rutas tampoco se alcanza mientras el comodín esté declarado antes; es el propio ejercicio demostrándose a sí mismo.
Conclusión
Escena Viva ya tiene una API organizada: tres routers montados bajo /api, controladores que solo traducen HTTP a dominio, router.param() cargando el evento una sola vez y dejándolo en req.evento, y router.route() agrupando los métodos de cada recurso con el 405 y su cabecera Allow gratis. Has visto que :id y req.params son tu compilarPatron mejor resuelto, que los comodines de Express 5 exigen nombre y siguen necesitando resolverDentroDe, que req.query es de solo lectura y por qué eso rompe código antiguo, y que el orden de declaración de las rutas es una regla que Express hereda, no que resuelve.
Queda un cabo suelto muy visible: POST /api/pedidos lee req.body, y req.body no existe por sí solo: aparece únicamente si antes de la ruta se ha ejecutado express.json(). Ese "antes de la ruta" es la puerta al concepto central del framework. En la siguiente lección, Middleware, veremos qué es exactamente esa cadena de funciones (req, res, next) que atraviesa cada petición: cómo se recorre, qué significa next(), next(error) y —el error más frustrante de Express— no llamar a next() en absoluto. Escribiremos los middleware propios de Escena Viva (registro con duración, identificador de petición, control de caché) y desmontaremos express.json() y express.static() comparándolos con el cuerpo.js y el estaticos.js que escribiste a mano.
Curso de Node.js: De Principiante a Avanzado
Módulo 1: Introducción a Node.js
- ¿Qué es Node.js?
- Instalación y Configuración del Entorno
- Tu Primer Programa en Node.js
- El REPL de Node.js
- JavaScript Moderno para Node.js
- El Proyecto del Curso: la Plataforma Escena Viva
Módulo 2: Conceptos Básicos
- Arquitectura de Node.js
- El Bucle de Eventos (Event Loop)
- Callbacks y Programación Asíncrona
- Promesas y async/await
- Eventos y EventEmitter
- Módulos CommonJS y require()
- Módulos ES e Interoperabilidad
Módulo 3: Sistema de Archivos y E/S
- Lectura y Escritura de Archivos
- El Módulo fs a Fondo
- Rutas Multiplataforma con el Módulo path
- Trabajando con Streams
- Streams de Transformación y pipeline
- Buffers y Datos Binarios
Módulo 4: HTTP y Servidores Web
- Creando un Servidor HTTP Simple
- Manejo de Solicitudes y Respuestas
- Enrutamiento Manual
- Sirviendo Archivos Estáticos
- Recibiendo Datos: Cuerpos de Petición y JSON
- Consumiendo APIs Externas desde Node.js
Módulo 5: NPM y Gestión de Paquetes
- Introducción a NPM y package.json
- Instalación y Uso de Paquetes
- Versionado Semántico y package-lock
- Scripts de npm y Automatización del Proyecto
- Creación y Publicación de Paquetes
- Seguridad y Mantenimiento de Dependencias
Módulo 6: Framework Express.js
- Introducción a Express.js
- Configuración de una Aplicación Express
- Enrutamiento en Express
- Middleware
- Middleware de Terceros Esenciales
- Validación de Datos de Entrada
- Manejo de Errores
Módulo 7: Bases de Datos y ORMs
- Introducción a las Bases de Datos
- Usando MongoDB con Mongoose
- Operaciones CRUD
- Relaciones, Poblado y Consultas Avanzadas
- Usando Bases de Datos SQL con Sequelize
- Migraciones, Transacciones y Datos de Prueba
Módulo 8: Autenticación y Autorización
- Introducción a la Autenticación
- Registro de Usuarios y Hash de Contraseñas
- Sesiones y Cookies con Passport.js
- Autenticación con JWT
- Control de Acceso Basado en Roles
- Buenas Prácticas de Seguridad en APIs
Módulo 9: Pruebas y Depuración
- Introducción a las Pruebas
- Pruebas Unitarias con Mocha y Chai
- Dobles de Prueba con Sinon
- Pruebas de Integración
- Cobertura y Automatización de las Pruebas
- Depuración de Aplicaciones Node.js
Módulo 10: Temas Avanzados
- El Módulo Cluster
- Hilos de Trabajo (Worker Threads)
- Caché y Colas de Trabajo con Redis
- Optimización del Rendimiento
- Construcción de APIs RESTful
- GraphQL con Node.js
Módulo 11: Despliegue y DevOps
- Configuración y Variables de Entorno
- Registro y Monitorización en Producción
- Usando PM2 para la Gestión de Procesos
- Empaquetado con Docker
- Desplegando en Heroku y Otras PaaS
- Integración y Despliegue Continuos
