Al final de la lección anterior quedó un cabo suelto: POST /api/pedidos lee req.body, pero req.body no existe salvo que algo lo haya creado antes. Ese "algo" es un middleware, y el middleware no es una característica más de Express: es Express. Todo lo que hace el framework —enrutar, leer cuerpos, servir estáticos, manejar errores— está construido sobre el mismo mecanismo: una lista de funciones (req, res, next) que se recorren en orden y se van pasando el testigo. En esta lección desmontamos ese mecanismo hasta el fondo, escribimos los middleware propios de Escena Viva y comparamos los integrados de Express con el cuerpo.js y el estaticos.js que programaste a mano en el módulo 4.
Contenido
- Qué es un middleware
- El recorrido de una petición
next(),next(error)y no llamar anext()- Tipos de middleware
- El orden de registro es el orden de ejecución
- Middleware con ruta de montaje
- Los middleware propios de Escena Viva
- Middleware integrados:
json,urlencoded,static - Middleware asíncrono
- Factorías de middleware
- Qué es un middleware
Un middleware es una función con esta firma:
function miMiddleware(peticion, respuesta, siguiente) {
// Puede hacer cuatro cosas:
// 1. LEER la peticion y la respuesta.
// 2. MODIFICAR ambas (añadir propiedades, cabeceras...).
// 3. CORTOCIRCUITAR: responder y terminar la cadena.
// 4. PASAR EL TESTIGO con siguiente(), o desviarse con siguiente(error).
siguiente();
}
// Y se registra de tres formas:
aplicacion.use(miMiddleware); // para todas las peticiones
aplicacion.use('/api', miMiddleware); // solo bajo /api
aplicacion.get('/api/eventos', miMiddleware, listar); // solo en esta rutaLa clave conceptual: una ruta también es un middleware. app.get('/salud', manejador) añade a la misma pila una entrada que, además del método y el patrón, tiene una función con la misma firma; cuando Express procesa una petición recorre una única lista donde conviven middleware y rutas. Esto ya lo hiciste en el módulo 4 sin llamarlo así: encadenabas leerCuerpo antes del manejador de POST /pedidos y comprobabas el tipo de contenido, todo antes de la lógica. Express convierte ese patrón en el mecanismo central del framework.
- El recorrido de una petición
flowchart TD
A["Peticion HTTP entrante"] --> B["idPeticion<br/>(cabecera X-Id-Peticion)"]
B --> C["registroPeticiones<br/>(mide con res.on finish)"]
C --> D["express.json()<br/>(rellena req.body)"]
D --> E{"Encaja alguna ruta?"}
E -- "si" --> F["Middleware de la ruta<br/>(validar, limitar...)"]
F --> G["Controlador"]
G --> H["res.json() → respuesta"]
E -- "no" --> I["Middleware 404"]
I --> J["Manejador de errores<br/>(err, req, res, next)"]
F -- "siguiente(error)" --> J
G -- "throw / rechazo" --> J
D -- "JSON malformado" --> J
J --> K["Respuesta de error<br/>{ error: { codigo, mensaje, estado } }"]
Dos caminos, uno normal y uno de error. Un middleware puede saltar del normal al de error en cualquier punto, y nunca se vuelve atrás: una vez dentro del manejador de errores, ya no se ejecutan los middleware normales que quedaban.
next(), next(error) y no llamar a next()
next(), next(error) y no llamar a next()Estas opciones son toda la gramática del mecanismo.
| Acción | Efecto | Cuándo usarla |
|---|---|---|
siguiente() |
Continúa con la siguiente entrada de la pila que encaje | El middleware ha hecho su trabajo y la petición sigue |
siguiente(error) |
Salta al primer middleware de error | Algo ha fallado y no puedes continuar |
siguiente('router') |
Sale del router actual y continúa en el padre | Poco frecuente: "este router no atiende esto" |
Responder sin siguiente() |
Termina la cadena ahí | Es lo correcto en un controlador o al cortocircuitar |
Ni responder ni siguiente() |
La petición se queda colgada | Nunca. Es siempre un error |
El error más frustrante del framework
// MAL: hay un camino que no responde ni continua.
aplicacion.use((peticion, respuesta, siguiente) => {
if (peticion.get('X-Api-Key')) siguiente();
// Si NO hay cabecera, no pasa nada. Ni respuesta, ni error, ni siguiente.
// El cliente espera hasta que expira su tiempo de espera. Sin logs. Sin pistas.
});
// BIEN: todos los caminos terminan.
aplicacion.use((peticion, respuesta, siguiente) => {
if (!peticion.get('X-Api-Key')) {
siguiente(Object.assign(new Error('Falta la clave de API'), { codigo: 'PARAMETRO_INVALIDO' }));
return; // el return evita seguir ejecutando por accidente
}
siguiente();
});Síntomas de la versión mala: curl se queda quieto, el navegador muestra la ruedecita eternamente, no aparece ningún error en la consola del servidor y el proceso está perfectamente sano. Es desconcertante precisamente porque no hay error: simplemente nadie ha terminado la respuesta. Para diagnosticarlo:
// detectorDeColgadas: registralo el PRIMERO en desarrollo.
aplicacion.use((peticion, respuesta, siguiente) => {
const temporizador = setTimeout(() => {
if (!respuesta.headersSent) {
console.error(`[colgada] ${peticion.method} ${peticion.originalUrl} lleva 5s sin responder`);
}
}, 5000);
const limpiar = () => clearTimeout(temporizador);
respuesta.on('finish', limpiar);
respuesta.on('close', limpiar);
siguiente();
});Así, una petición colgada deja rastro en stderr con su método y su ruta, y ya solo tienes que mirar qué middleware de esa ruta tiene un if sin salida.
Regla: después de
siguiente(...), escribereturnsalvo que sea la última línea. Llamar asiguiente()dos veces provoca el avisoCannot set headers after they are sent to the client, otro clásico difícil de rastrear.
- Tipos de middleware
| Tipo | Cómo se registra | Ejemplo en Escena Viva |
|---|---|---|
| De aplicación / de router | app.use(fn), app.get(ruta, fn, ...), router.use(fn) |
idPeticion, registroPeticiones; limitador solo en rutasPedidos |
| De error | app.use((err, req, res, next) => ...) |
El manejador central de 06-07 |
| Integrado / de terceros | Viene con Express o de un paquete npm | express.json, express.static; helmet, cors, morgan (06-05) |
Todos son la misma cosa: funciones en la pila. La única que se distingue por su forma es la de error, que tiene cuatro parámetros; Express cuenta los argumentos (fn.length) para decidir si es normal o de error, y por eso olvidar el cuarto parámetro hace que tu manejador nunca se ejecute (06-07).
- El orden de registro es el orden de ejecución
No hay prioridades ni resolución inteligente: Express recorre la pila de arriba abajo, en el orden exacto en que registraste cada entrada.
// MAL: la ruta se registra ANTES del lector de cuerpo.
aplicacion.post('/api/pedidos', (peticion, respuesta) => {
// peticion.body es undefined: express.json() aun no se ha ejecutado.
respuesta.json({ recibido: peticion.body.sesionId });
// TypeError: Cannot read properties of undefined (reading 'sesionId') -> 500
});
aplicacion.use(express.json());
// BIEN: primero lo transversal, despues las rutas.
aplicacion.use(express.json()); // 1. rellena req.body
aplicacion.post('/api/pedidos', (peticion, respuesta) => {
respuesta.json({ recibido: peticion.body.sesionId }); // 2. ya existe
});En la versión mala el middleware está registrado, sí, pero después de la ruta: como la ruta responde, la cadena termina antes de llegar a él.
El orden canónico de crearAplicacion(), que iremos completando y cerraremos en 06-05: ajustes (disable('x-powered-by'), set('trust proxy')), seguridad y cabeceras (helmet, cors), observabilidad (idPeticion, registroPeticiones), análisis del cuerpo (express.json), estáticos (express.static), rutas de la API, middleware 404 y, siempre el último, el manejador de errores.
- Middleware con ruta de montaje
aplicacion.use(fn) se ejecuta siempre; aplicacion.use('/api', fn) solo si la ruta empieza por /api. El montaje con prefijo tiene un efecto que sorprende: dentro del middleware montado, req.url pierde el prefijo.
Dentro de un middleware montado en /api, para GET /api/eventos/evt-001?x=1:
| Propiedad | Contenido | Úsala para |
|---|---|---|
req.originalUrl |
/api/eventos/evt-001?x=1 (siempre completa) |
Registros y trazas |
req.baseUrl |
/api (el punto de montaje) |
Construir URLs absolutas |
req.url |
/eventos/evt-001?x=1 (relativa al montaje) |
Lógica interna del router |
req.path |
/eventos/evt-001 (sin query) |
Comparaciones de ruta |
Esa reescritura es lo que permite que src/rutas/eventos.js declare router.get('/:id') sin saber que estará montado en /api/eventos. Es la misma técnica que usa express.static: montado en /descargas, busca en el disco solo la parte relativa.
Consejo de registro: en
registro-peticiones.jsusa siemprereq.originalUrl. Conreq.urldentro de un montaje, tus logs mostrarán rutas incompletas y no podrás correlacionarlas con lo que ve el cliente.
- Los middleware propios de Escena Viva
src/middleware/id-peticion.js
Genera un identificador único por petición: la base de la trazabilidad, que aparecerá en los registros, en las respuestas de error (06-07) y en el registro estructurado del módulo 11.
// src/middleware/id-peticion.js
const { randomUUID } = require('node:crypto');
const CABECERA = 'X-Id-Peticion';
// Asigna un identificador unico a cada peticion. Si el cliente (o un proxy)
// ya envio uno valido lo respeta, para que la traza sobreviva a varios servicios.
function idPeticion(peticion, respuesta, siguiente) {
const recibido = peticion.get(CABECERA);
const esValido = typeof recibido === 'string' && /^[\w-]{8,64}$/.test(recibido);
const identificador = esValido ? recibido : randomUUID();
peticion.idPeticion = identificador;
respuesta.locals.idPeticion = identificador;
respuesta.setHeader(CABECERA, identificador);
siguiente();
}
module.exports = { idPeticion, CABECERA_ID_PETICION: CABECERA };Detalle importante: se valida el identificador recibido. Aceptar a ciegas una cabecera del cliente permitiría inyectar saltos de línea en los registros o valores enormes; la regex acota longitud y caracteres.
src/middleware/registro-peticiones.js
Mide la duración real de cada petición y la escribe por stderr, según la convención del curso.
// src/middleware/registro-peticiones.js
// Registra metodo, ruta, estado, tamaño y duracion de cada peticion.
function crearRegistroPeticiones({ omitirRutas = ['/api/salud'] } = {}) {
return function registroPeticiones(peticion, respuesta, siguiente) {
if (omitirRutas.includes(peticion.originalUrl)) {
siguiente();
return;
}
// hrtime.bigint da nanosegundos monotonos: no le afectan los cambios de hora.
const inicio = process.hrtime.bigint();
const etiqueta = `${peticion.idPeticion ?? '-'} ${peticion.method} ${peticion.originalUrl}`;
// 'finish' se dispara cuando la respuesta se ha entregado por completo.
respuesta.on('finish', () => {
const ms = (Number(process.hrtime.bigint() - inicio) / 1e6).toFixed(1);
const bytes = respuesta.getHeader('Content-Length') ?? 0;
console.error(`[peticion] ${etiqueta} ${respuesta.statusCode} ${bytes}b ${ms}ms`);
});
// 'close' sin 'finish' significa que el cliente aborto antes de tiempo.
respuesta.on('close', () => {
if (!respuesta.writableFinished) console.error(`[peticion] ${etiqueta} ABORTADA`);
});
siguiente();
};
}Por qué res.on('finish') y no medir tras siguiente(): porque siguiente() devuelve el control en cuanto el siguiente middleware se suspende en una operación asíncrona, no cuando la respuesta se ha enviado. El evento finish de ServerResponse —el mismo objeto stream del módulo 3— es el único punto fiable.
src/middleware/sin-cache.js
// src/middleware/sin-cache.js
// Marca como no cacheable lo que cambia constantemente: aforos y pedidos.
function sinCache(peticion, respuesta, siguiente) {
const cabeceras = { 'Cache-Control': 'no-store, no-cache, must-revalidate', Pragma: 'no-cache' };
respuesta.set({ ...cabeceras, Expires: '0' });
siguiente();
}
module.exports = { sinCache };
// Se monta solo donde hace falta, no globalmente (src/rutas/index.js):
api.use('/pedidos', sinCache, rutasPedidos);
api.use('/sesiones', sinCache, rutasSesiones); // el aforo cambia a cada venta
api.use('/eventos', rutasEventos); // el catalogo si puede cachearseQue la disponibilidad de la Sala Bóveda se sirva desde una caché de treinta segundos es exactamente cómo se vende una entrada que ya no existe.
En src/app.js quedan registrados en este orden: aplicacion.use(idPeticion) primero, porque todo lo demás lo usa; aplicacion.use(crearRegistroPeticiones()) segundo, para medir desde lo antes posible; y después express.json({ limit }).
- Middleware integrados
express.json() frente a tu cuerpo.js
Se configura con express.json({ limit: '100kb', type: 'application/json', strict: true }): limit equivale a tu LIMITE_BYTES, type acota qué Content-Type interpreta y strict acepta solo objetos y arrays en la raíz. Comparación honesta con lo que escribiste en el módulo 4:
| Aspecto | Tu leerCuerpo |
express.json() |
|---|---|---|
| Acumular los trozos del stream | req.on('data') y concatenar Buffers |
Igual, internamente |
| Límite de tamaño y respuesta al superarlo | LIMITE_BYTES y CUERPO_DEMASIADO_GRANDE → 413 |
Opción limit; error con status: 413 y type: 'entity.too.large' |
| JSON malformado | JSON_INVALIDO → 400 |
Error con status: 400 y type: 'entity.parse.failed' |
Content-Type incorrecto |
TIPO_NO_ACEPTADO → 415 |
No falla: deja req.body como {} |
| Codificaciones y cuerpo crudo | No lo cubriste | charset y gzip incluidos; opción verify para firmas de webhooks |
Es tu mismo diseño con más casos límite cubiertos. Un cuerpo que supera el límite devuelve 413 y un JSON roto devuelve 400; esos errores llegan al manejador central como cualquier otro, y en 06-07 los traduciremos a nuestro formato mirando su propiedad type. Un detalle que sorprende: si el Content-Type no es JSON, express.json() no protesta, simplemente no hace nada y req.body queda como {}. Si quieres el 415 del módulo 4, hay que exigirlo explícitamente:
// src/middleware/exigir-json.js
function exigirJson(peticion, respuesta, siguiente) {
if (['GET', 'HEAD', 'DELETE'].includes(peticion.method) || peticion.is('application/json')) {
siguiente();
return;
}
const mensaje = 'Se esperaba Content-Type: application/json';
siguiente(Object.assign(new Error(mensaje), { codigo: 'TIPO_NO_ACEPTADO' }));
}express.urlencoded()
Interpreta formularios HTML clásicos con aplicacion.use(express.urlencoded({ extended: true, limit: '20kb' })). Con extended: true usa la biblioteca qs y admite estructuras anidadas (filtro[sala]=Sala+Boveda); con false usa querystring del núcleo y todo queda plano. Escena Viva es una API JSON y no lo necesita, pero conviene conocerlo por si añades un formulario de contacto.
express.static() frente a tus estaticos.js + tipos-mime.js
aplicacion.use(express.static(configuracion.directorioPublico, {
index: 'index.html', // que servir en el directorio raiz
maxAge: '1h', // Cache-Control: public, max-age=3600
etag: true, // ETag y 304 automatico (con lastModified, If-Modified-Since)
dotfiles: 'ignore', // no sirve .env ni .git
fallthrough: true, // si no existe el fichero, continua la cadena (404 propio)
extensions: ['html'], // /contacto encuentra contacto.html
}));| Lo que hacías a mano | Lo que hace express.static |
|---|---|
resolverDentroDe contra el recorrido de directorios |
Comprobación equivalente incorporada |
Mapa de extensión a MIME (tipos-mime.js) |
mime-types, con centenares de tipos |
createReadStream + pipeline |
Igual, con soporte de rangos (Range) para vídeo |
Hash del contenido para el ETag y comparar If-None-Match |
ETag débil por tamaño y fecha (más barato) y 304 automático |
| Comprimir con gzip a mano | No lo hace: es trabajo de compression (06-05) |
Lo que hace mejor que tu versión: rangos HTTP, dotfiles, fallthrough (que permite que un fichero inexistente siga hacia tu 404 en lugar de responder ahí mismo) y una lista de tipos MIME que nunca querrás mantener a mano. Lo que tu versión hacía y esta no: la compresión, delegada a un middleware específico. Es una separación de responsabilidades mejor que la tuya.
- Middleware asíncrono
Un middleware puede ser async y en Express 5 esto simplemente funciona: si obtenerCatalogo rechaza (fichero ilegible, JSON corrupto) en async function cargarCatalogo(peticion, respuesta, siguiente) { peticion.catalogo = await obtenerCatalogo(); siguiente(); }, Express captura la promesa rechazada y llama a siguiente(error) por ti.
En Express 4 ese código dejaba la petición colgada si la promesa rechazaba, y había que envolver cada manejador con const asyncHandler = (fn) => (p, r, s) => Promise.resolve(fn(p, r, s)).catch(s). Si te encuentras asyncHandler, express-async-handler o wrapAsync en un proyecto, ya sabes qué son: restos de Express 4 que en Express 5 sobran. Profundizaremos en 06-07.
La excepción que sigue siendo tuya: las devoluciones de llamada dentro de un manejador. Express solo puede capturar lo que rechaza la promesa que devuelves; no ve un throw dentro de un setTimeout ni dentro de un stream.on('data').
// MAL: Express no puede capturar esto. Tumba el proceso.
aplicacion.get('/mal', (peticion, respuesta) => {
setTimeout(() => { throw new Error('nadie me va a capturar'); }, 100);
});
// BIEN: promisificar (dormir.js del modulo 2) y dejar que el rechazo llegue.
aplicacion.get('/bien', async (peticion, respuesta) => {
await dormir(100);
throw new Error('este si llega al manejador de errores');
});
- Factorías de middleware
Un middleware fijo sirve para un caso. Una factoría —una función que devuelve un middleware— sirve para todos, y es el patrón que usan express.json({ limit }), helmet({ ... }) y cors({ ... }).
// src/middleware/exigir-cabecera.js
// Devuelve un middleware que exige una cabecera, opcionalmente con patron.
function exigirCabecera(nombre, { patron } = {}) {
// Todo lo caro se calcula UNA vez, aqui, no en cada peticion.
const nombreNormalizado = nombre.toLowerCase();
return function comprobarCabecera(peticion, respuesta, siguiente) {
const valor = peticion.headers[nombreNormalizado];
if (!valor || (patron && !patron.test(valor))) {
const mensaje = `Cabecera ${nombre} ausente o invalida`;
siguiente(Object.assign(new Error(mensaje), { codigo: 'PARAMETRO_INVALIDO' }));
return;
}
siguiente();
};
}
module.exports = { exigirCabecera };
// Uso: el mismo middleware, configurado de dos formas distintas.
const patron = /^(web|taquilla|telefono)$/;
rutasPedidos.post('/', exigirCabecera('X-Canal-Venta', { patron }), crearPedido);
rutasSesiones.use(exigirCabecera('Accept'));Tres ventajas concretas: es configurable sin duplicar código; hace el trabajo caro una sola vez (compilar regex, leer configuración, abrir recursos), de modo que el middleware devuelto solo hace lo mínimo por petición; y es probable, porque en el módulo 9 podrás llamar a exigirCabecera('X', {...}) y probar la función devuelta con objetos falsos, sin levantar un servidor.
Es exactamente el mismo patrón de crearRegistroPeticiones(opciones) que ya has escrito en el apartado 7. Cuando dudes entre exportar un middleware o una factoría, exporta la factoría: cuesta una línea más y te ahorra una refactorización.
Errores Comunes y Consejos
- No llamar a
next()en algún camino. La petición se cuelga sin errores. Usa el detector de colgadas en desarrollo y escribereturndespués de cadasiguiente(...). - Llamar a
next()y además responder. ProvocaCannot set headers after they are sent; suele venir de unreturnolvidado. - Registrar
express.json()después de las rutas.req.bodyseráundefined: el error número uno de los primerosPOST. - Registrar el manejador de errores en medio. Solo captura lo registrado antes que él; va siempre el último.
- Usar
req.urlen los registros dentro de un montaje. Verás rutas sin el prefijo: usareq.originalUrl. - Middleware globales que solo hacen falta en una ruta.
sinCacheen toda la aplicación destroza la caché de los estáticos; monta cada uno en el ámbito mínimo. - Trabajo caro dentro del middleware en lugar de en la factoría. Compilar una regex en cada petición es desperdicio puro.
- Confiar en cabeceras del cliente sin validar.
X-Id-Peticionviene de fuera: acota longitud y caracteres. - Consejo: un middleware debe hacer una cosa. Si el tuyo autentica, registra y comprime, son tres, y tres es el número de sitios donde buscarás el fallo.
Ejercicios
Ejercicio 1: cabecera de tiempo de servicio
Escribe src/middleware/tiempo-servicio.js con una factoría crearTiempoServicio() que añada la cabecera X-Tiempo-Servicio con los milisegundos que ha tardado la petición. Pista: no puedes fijar cabeceras en res.on('finish') porque ya se han enviado; hay que interceptar el momento justo antes envolviendo res.end.
Ejercicio 2: cazar la petición colgada
Crea una aplicación con tres middleware, donde el segundo tiene un camino que no llama a siguiente(). Comprueba con curl que la petición se queda colgada, añade el detector del apartado 3 y demuestra que el problema aparece en stderr con método y ruta.
Ejercicio 3: comparar express.static con tu versión
Sirve publico/ con express.static configurado con maxAge: '1h' y etag: true. Con curl -si comprueba: que la primera petición devuelve 200 con ETag y Cache-Control, que repitiéndola con -H 'If-None-Match: <etag>' devuelve 304 sin cuerpo, y que pedir /publico/../.env no sale del directorio. Contrasta el resultado con lo que hacía tu estaticos.js.
Soluciones
Solución 1
// src/middleware/tiempo-servicio.js
function crearTiempoServicio(cabecera = 'X-Tiempo-Servicio') {
return function tiempoServicio(peticion, respuesta, siguiente) {
const inicio = process.hrtime.bigint();
// Envolvemos res.end: es el ultimo instante en que aun se pueden
// fijar cabeceras, porque writeHead todavia no se ha ejecutado.
const finOriginal = respuesta.end;
respuesta.end = function (...argumentos) {
if (!respuesta.headersSent) {
const duracionMs = Number(process.hrtime.bigint() - inicio) / 1e6;
respuesta.setHeader(cabecera, duracionMs.toFixed(1));
}
return finOriginal.apply(this, argumentos);
};
siguiente();
};
}res.on('finish') no sirve porque para entonces las cabeceras ya viajaron por la red: setHeader lanzaría o sería ignorado. res.on('close') es aún peor, porque puede dispararse con la conexión ya cerrada. Este envoltorio de end es la técnica que usa morgan internamente.
Solución 2
// colgada.js
const aplicacion = require('express')();
aplicacion.use(detectorDeColgadas); // el del apartado 3, siempre el primero
// El culpable: solo continua si hay cabecera; si no, no hace nada.
aplicacion.use((peticion, respuesta, siguiente) => {
if (peticion.get('X-Sala')) siguiente();
});
aplicacion.get('/entradas', (p, r) => r.json({ sala: p.get('X-Sala') }));
aplicacion.listen(3000);curl -s --max-time 5 localhost:3000/entradas se cuelga y stderr muestra [colgada] GET /entradas; añadiendo -H 'X-Sala: Sala Boveda' responde {"sala":"Sala Boveda"}.
Solución 3
# 1. Primera peticion: 200 con Cache-Control: public, max-age=3600 y ETag.
curl -si localhost:3000/estilos.css | head -n 8
# 2. Repeticion condicional: 304 Not Modified, sin cuerpo.
curl -si -H 'If-None-Match: W/"1a4-1949f2c0a10"' localhost:3000/estilos.css | head -n 2
# 3. Intento de recorrido de directorios: 404, no sale del directorio.
curl -si 'localhost:3000/../.env' | head -n 1El tercer caso funciona porque express.static normaliza la ruta y rechaza lo que salga del directorio base, igual que tu resolverDentroDe; la diferencia es que ahora esa protección está en un paquete que millones de despliegues ejercitan a diario, no en veinte líneas tuyas.
Conclusión
El middleware es el mecanismo único sobre el que está construido todo Express: una pila de funciones (req, res, next) recorrida en el orden exacto de registro, donde cada una lee, modifica, cortocircuita o pasa el testigo, y donde next(error) abre un segundo camino del que no se vuelve. Has visto el diagrama completo del recorrido, el error más frustrante del framework —no llamar a next()— y cómo diagnosticarlo, la diferencia entre req.url, req.baseUrl y req.originalUrl al montar con prefijo, y por qué el orden de registro no admite excepciones.
Escena Viva tiene ya sus middleware propios: idPeticion para la trazabilidad, crearRegistroPeticiones midiendo con res.on('finish') y escribiendo por stderr, sinCache montado solo donde el aforo cambia, y exigirCabecera como ejemplo de factoría configurable. Y has comprobado que express.json() es tu cuerpo.js con más casos límite cubiertos, y que express.static() es tu estaticos.js más tipos-mime.js con soporte de rangos y dotfiles. Todos son tuyos hasta ahora.
En la siguiente lección, Middleware de Terceros Esenciales, montamos el kit mínimo que ninguna API debería salir a producción sin él: helmet y sus cabeceras de seguridad una a una, cors para que el publico/app.js de Escena Viva pueda llamar a la API desde otro origen, morgan para el registro HTTP, compression para el ancho de banda y express-rate-limit para que un script no agote el aforo del Auditorio Ribera en diez segundos. Cada uno pasado por el filtro de auditoría que aprendiste en el módulo 5, y todos ordenados en la versión definitiva de crearAplicacion().
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
