Qué es un middleware en Express
Un middleware es una función que se ejecuta durante el ciclo de vida de una petición HTTP, entre el momento en que llega al servidor y el momento en que se envía la respuesta. En Express, un middleware puede:
- Leer y modificar
req(request) yres(response). - Terminar la respuesta (por ejemplo, con
res.json(...)ores.status(...).send(...)). - Delegar el control al siguiente middleware con
next().
La idea clave: los middlewares son ideales para lógica transversal (cross-cutting), como logging, validación, cabeceras, autenticación, rate limiting, etc., sin repetir código en cada ruta.
Orden de ejecución y el papel de next()
Express ejecuta middlewares en el orden en que se registran. Ese orden depende de dónde los declares y cómo los apliques (global, router, ruta). Un middleware típico tiene esta firma:
function miMiddleware(req, res, next) { // ... lógica next();}Reglas prácticas sobre next()
- Si llamas a
next(), Express continúa con el siguiente middleware que coincida. - Si envías respuesta (
res.send,res.json,res.end), normalmente no debes llamar anext()después. - Si ocurre un error y quieres delegarlo al manejador de errores, usa
next(err).
Visualizando el flujo
Imagina esta cadena:
app.use(mwA);app.use(mwB);app.get('/ruta', mwC, handlerFinal);Para GET /ruta, el orden será: mwA -> mwB -> mwC -> handlerFinal (siempre que cada uno llame a next() y no corte la respuesta).
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
Guía práctica paso a paso: middlewares típicos
A continuación implementarás middlewares comunes y los aplicarás a distintos niveles. Los ejemplos asumen que ya tienes una app Express creada y funcionando, y que puedes editar tu archivo principal (por ejemplo app.js o server.js).
Paso 1: Middleware de logging de requests
Objetivo: registrar método, ruta y tiempo de respuesta. Este middleware es global porque suele interesar para todas las peticiones.
// middleware/logRequest.jsfunction logRequest(req, res, next) { const start = Date.now(); res.on('finish', () => { const ms = Date.now() - start; console.log(`${req.method} ${req.originalUrl} - ${res.statusCode} (${ms}ms)`); }); next();}module.exports = { logRequest };Aplicación global:
const express = require('express');const { logRequest } = require('./middleware/logRequest');const app = express();app.use(logRequest);res.on('finish') se dispara cuando la respuesta ya se envió, por eso es útil para medir tiempo y status final.
Paso 2: Middleware para manejo de cabeceras (headers)
Objetivo: añadir cabeceras comunes. Por ejemplo, un header didáctico y permitir CORS básico (en un entorno real podrías usar librerías específicas, pero aquí lo hacemos a mano para entender el concepto).
// middleware/headers.jsfunction setCommonHeaders(req, res, next) { res.setHeader('X-Powered-By', 'Express'); // CORS básico (didáctico) res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-API-Key'); res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'); // Responder preflight OPTIONS sin pasar a rutas if (req.method === 'OPTIONS') { return res.sendStatus(204); } next();}module.exports = { setCommonHeaders };Aplicación global (normalmente antes de tus rutas):
const { setCommonHeaders } = require('./middleware/headers');app.use(setCommonHeaders);Paso 3: Middleware de validación básica
Objetivo: validar datos de entrada sin repetir lógica. Un patrón útil es crear un factory (función que devuelve middleware) para reutilizarlo con diferentes reglas.
Ejemplo: validar que exista un campo en req.body. (Asumiendo que ya usas express.json() en tu app; si no, las validaciones sobre req.body no verán datos).
// middleware/validate.jsfunction requireBodyFields(fields = []) { return function (req, res, next) { const missing = fields.filter((f) => req.body?.[f] === undefined); if (missing.length > 0) { return res.status(400).json({ error: 'VALIDATION_ERROR', message: `Faltan campos requeridos: ${missing.join(', ')}` }); } next(); };}module.exports = { requireBodyFields };Uso por ruta (solo donde aplica):
const { requireBodyFields } = require('./middleware/validate');app.post('/users', requireBodyFields(['name', 'email']), (req, res) => { // aquí ya sabes que name y email existen res.status(201).json({ ok: true, user: req.body });});Paso 4: Autenticación simulada con una clave en header
Objetivo: proteger endpoints con un mecanismo simple para fines didácticos. Usaremos un header X-API-Key y compararemos con una clave esperada. No es un sistema de seguridad real, pero sirve para entender el flujo de autenticación con middlewares.
// middleware/auth.jsfunction requireApiKey(expectedKey) { return function (req, res, next) { const apiKey = req.header('X-API-Key'); if (!apiKey) { return res.status(401).json({ error: 'UNAUTHORIZED', message: 'Falta X-API-Key' }); } if (apiKey !== expectedKey) { return res.status(403).json({ error: 'FORBIDDEN', message: 'X-API-Key inválida' }); } // Puedes adjuntar info al request para usarla luego req.auth = { type: 'apiKey', keyId: 'demo' }; next(); };}module.exports = { requireApiKey };Uso por router o por ruta (según lo que quieras proteger).
Aplicación de middlewares: global, por router y por ruta
1) Middlewares globales (app.use)
Se ejecutan para todas las rutas que coincidan con el path (si no pasas path, coincide con todo). Útil para logging, headers comunes, parseo, etc.
const express = require('express');const { logRequest } = require('./middleware/logRequest');const { setCommonHeaders } = require('./middleware/headers');const app = express();app.use(express.json());app.use(logRequest);app.use(setCommonHeaders);2) Middlewares por router (router.use)
Cuando tienes un conjunto de rutas bajo un prefijo (por ejemplo /admin), puedes aplicar middlewares solo a ese grupo.
const express = require('express');const { requireApiKey } = require('./middleware/auth');const adminRouter = express.Router();adminRouter.use(requireApiKey(process.env.ADMIN_API_KEY || 'dev-secret'));adminRouter.get('/stats', (req, res) => { res.json({ ok: true, auth: req.auth, stats: { users: 10 } });});module.exports = { adminRouter };Montaje del router:
const { adminRouter } = require('./routes/admin');app.use('/admin', adminRouter);Resultado: solo las rutas que empiecen por /admin requieren la clave.
3) Middlewares por ruta (en la definición de la ruta)
Útil cuando solo una ruta específica necesita validación o autenticación.
const { requireBodyFields } = require('./middleware/validate');const { requireApiKey } = require('./middleware/auth');app.post('/payments', requireApiKey(process.env.PAYMENTS_KEY || 'payments-dev'), requireBodyFields(['amount', 'currency']), (req, res) => { res.status(201).json({ ok: true, payment: req.body }); });El orden importa: primero autenticas, luego validas, luego ejecutas el handler.
Composición sin duplicación: reutiliza cadenas de middlewares
Cuando varias rutas comparten la misma cadena de middlewares, evita copiar y pegar. Dos técnicas comunes:
Técnica A: arreglo de middlewares reutilizable
const { requireApiKey } = require('./middleware/auth');const { requireBodyFields } = require('./middleware/validate');const protectPayments = [ requireApiKey(process.env.PAYMENTS_KEY || 'payments-dev')];const validatePaymentBody = [ requireBodyFields(['amount', 'currency'])];app.post('/payments', ...protectPayments, ...validatePaymentBody, (req, res) => { res.status(201).json({ ok: true });});app.put('/payments/:id', ...protectPayments, ...validatePaymentBody, (req, res) => { res.json({ ok: true, id: req.params.id });});Ventaja: composición flexible y legible.
Técnica B: sub-router con middlewares comunes
Si un conjunto de endpoints comparten protección y/o headers específicos, un router reduce repetición.
const express = require('express');const { requireApiKey } = require('./middleware/auth');const { requireBodyFields } = require('./middleware/validate');const paymentsRouter = express.Router();paymentsRouter.use(requireApiKey(process.env.PAYMENTS_KEY || 'payments-dev'));paymentsRouter.post('/', requireBodyFields(['amount', 'currency']), (req, res) => { res.status(201).json({ ok: true, payment: req.body });});paymentsRouter.put('/:id', requireBodyFields(['amount', 'currency']), (req, res) => { res.json({ ok: true, id: req.params.id, payment: req.body });});module.exports = { paymentsRouter };Montaje:
const { paymentsRouter } = require('./routes/payments');app.use('/payments', paymentsRouter);Ventaja: el middleware de autenticación se declara una sola vez para todo el grupo.
Manejo de errores con middleware (patrón esencial)
Además de middlewares “normales”, Express soporta middlewares de error con 4 parámetros: (err, req, res, next). Se ejecutan cuando llamas next(err) o cuando ocurre un error en un middleware async (si lo capturas y lo pasas a next).
// middleware/errorHandler.jsfunction errorHandler(err, req, res, next) { console.error('Error:', err); res.status(500).json({ error: 'INTERNAL_ERROR' });}module.exports = { errorHandler };Debe registrarse al final (después de rutas):
const { errorHandler } = require('./middleware/errorHandler');app.use(errorHandler);Ejemplo integrado: orden recomendado de registro
Este ejemplo muestra un orden típico: parseo, logging, headers, rutas públicas, routers protegidos, y al final el manejador de errores.
const express = require('express');const { logRequest } = require('./middleware/logRequest');const { setCommonHeaders } = require('./middleware/headers');const { requireBodyFields } = require('./middleware/validate');const { requireApiKey } = require('./middleware/auth');const { errorHandler } = require('./middleware/errorHandler');const app = express();app.use(express.json());app.use(logRequest);app.use(setCommonHeaders);app.get('/health', (req, res) => res.json({ ok: true }));app.post('/contact', requireBodyFields(['email', 'message']), (req, res) => { res.status(201).json({ ok: true });});app.get('/private', requireApiKey(process.env.API_KEY || 'dev-secret'), (req, res) => { res.json({ ok: true, auth: req.auth });});app.use(errorHandler);module.exports = { app };Si notas comportamientos inesperados (por ejemplo, una ruta no se ejecuta), revisa primero el orden y si algún middleware está terminando la respuesta sin llamar a next().