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

  1. Qué es un middleware
  2. El recorrido de una petición
  3. next(), next(error) y no llamar a next()
  4. Tipos de middleware
  5. El orden de registro es el orden de ejecución
  6. Middleware con ruta de montaje
  7. Los middleware propios de Escena Viva
  8. Middleware integrados: json, urlencoded, static
  9. Middleware asíncrono
  10. Factorías de middleware

  1. 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 ruta

La 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.

  1. 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.

  1. 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(...), escribe return salvo que sea la última línea. Llamar a siguiente() dos veces provoca el aviso Cannot set headers after they are sent to the client, otro clásico difícil de rastrear.

  1. 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).

  1. 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.

  1. 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.js usa siempre req.originalUrl. Con req.url dentro de un montaje, tus logs mostrarán rutas incompletas y no podrás correlacionarlas con lo que ve el cliente.

  1. 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 cachearse

Que 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 }).

  1. 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.

  1. 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');
});

  1. 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 escribe return después de cada siguiente(...).
  • Llamar a next() y además responder. Provoca Cannot set headers after they are sent; suele venir de un return olvidado.
  • Registrar express.json() después de las rutas. req.body será undefined: el error número uno de los primeros POST.
  • Registrar el manejador de errores en medio. Solo captura lo registrado antes que él; va siempre el último.
  • Usar req.url en los registros dentro de un montaje. Verás rutas sin el prefijo: usa req.originalUrl.
  • Middleware globales que solo hacen falta en una ruta. sinCache en 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-Peticion viene 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 1

El 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

Módulo 2: Conceptos Básicos

Módulo 3: Sistema de Archivos y E/S

Módulo 4: HTTP y Servidores Web

Módulo 5: NPM y Gestión de Paquetes

Módulo 6: Framework Express.js

Módulo 7: Bases de Datos y ORMs

Módulo 8: Autenticación y Autorización

Módulo 9: Pruebas y Depuración

Módulo 10: Temas Avanzados

Módulo 11: Despliegue y DevOps

Módulo 12: Proyectos del Mundo Real

© Copyright 2026. Todos los derechos reservados