Implementación del patrón middleware en Node.js sin Express
Resumen
El patrón middleware es fundamental en el ecosistema de Node.js y especialmente utilizado por el framework Express. Este patrón permite ejecutar una secuencia ordenada de funciones, conocidas como middlewares, sobre cada petición (request) recibida. Cabe destacar que cada middleware puede modificar los objetos recibidos y decidir si continuar o detener la ejecución secuencialmente.
¿Qué es el patrón middleware en Node.js?
El middleware es un patrón que facilita ejecutar una serie de acciones secuencialmente durante la gestión de una petición en aplicaciones Node.js. Aunque popular por su uso en Express, este patrón puede implementarse fácilmente en aplicaciones puras de Node.js, sin necesidad del framework específico.
Cada middleware:
Recibe generalmente tres parámetros principales:
El objeto de petición (request).
El objeto de respuesta (response).
Una función llamada next que continúa la cadena de ejecución.
Realiza tareas específicas como autenticación, validación o procesamiento.
Puede modificar los objetos recibidos antes de pasar al siguiente middleware.
¿Cómo implementar middlewares sin Express?
Para demostrar el funcionamiento de middleware sin utilizar Express, se puede crear una función propia llamada runMiddlewares. A continuación, se describe paso a paso cómo hacerlo en una aplicación sencilla Node.js.
Creación de la función runMiddlewares
Inicialmente, es imprescindible definir una función como la siguiente:
Toma como argumentos el objeto request, el objeto response y un arreglo de funciones middleware.
Ejecuta cada middleware incrementando un índice para avanzar en la secuencia.
Utiliza recursion con la función next para controlar el flujo.
¿Cómo definir y ejecutar varios middlewares?
A continuación, definimos diferentes middlewares simulando tareas prácticas habituales en una solicitud HTTP.
constmiddlewareUno=(req, res, next)=>{console.log('autenticación de la solicitud');next();};constmiddlewareDos=(req, res, next)=>{console.log('procesamiento de la petición');next();};constmiddlewareTres=(req, res, next)=>{console.log('finalización');next();};
Posteriormente, ejecutas la función:
const req ={};const res ={};runMiddlewares(req, res,[middlewareUno, middlewareDos, middlewareTres]);
Al lanzar este script, verás en la consola los mensajes indicando la ejecución secuencial de cada middleware.
¿Cómo implementar middlewares condicionales?
Un middleware puede decidir, según ciertas condiciones establecidas en tiempo de ejecución, si continúa ejecutándose la cadena de middlewares siguientes o no. Como ejercicio, puedes añadir un cuarto middleware cuya ejecución dependerá de la lógica definida en el middleware anterior.
Este tipo de lógica ayuda a gestionar adecuadamente diferentes escenarios que podrían surgir en el ciclo de vida de una petición web.
Profe: al llegar a este capitulo noté lo siguiente: Si bien el contenido es avanzado y particularmente entiendo el aprendizaje basado en ejemplos de código por mi nivel personal, es necesario explicar los conceptos previos de lo que se va a implementar que den contexto de la característica de lenguaje (con un diagrama es suficiente), eso les hace aún mas enriquecedor el contenido explicado. Muy buen contenido!
Yo no sabia que los Middlewares se implementaban de esa manera.
Aqui esta mi implementación de como creo que lo puede hacer Express
Aqui mi respuesta super sencilla al ejercicio de ejecutar un middleware en base a una condición:
functionrunMiddlewares(request, response, middlewares){let index =0;constnext=()=>{if(index < middlewares.length){const middleware = middlewares[index++];middleware(req, res, next)}};next();}constmiddleware1=(req, res, next)=>{console.log(`Middleware 1: Autenticación de la petición`);next();}constmiddleware2=(req, res, next)=>{console.log(`Middleware 2: Procesamiento de la petición`);next();}constmiddleware3=(req, res, next)=>{console.log(`Middleware 3: Validando si valor es par`);if(req.valor%2===0){console.log(`Middleware 3: El valor es par`);next();}else{console.log(`Middleware 3: El valor es impar y finalización de la petición`);}}constmiddleware4=(req, res, next)=>{console.log(`Middleware 4: Finalizando la petición`);next();}const req ={valor:4};const res ={};runMiddlewares(req, res,[middleware1, middleware2, middleware3, middleware4]);
Creo que nunca me había detenido a pensar en los middlewares de esta manera, me parece interesante que es básicamente el patrón de "Chain of responsibility" aplicado a un caso puntual en el ciclo de vida del procesamiento de la entrada.
Para simplificar, los middlewares basicamente son funciones que se ejecutan en cadena hasta que es ejecutado el metodo res.send() o res.json(). El objeto req trae la informacion del request (headers, datos que mando el usuario e informacion importante del cliente) pero este objeto tambien puede ser manipulado por el developer y agregarle propiedades que pueden ser usadas en los siguientes middlewares (por ejemplo informacion del usuario como su id, su nombre o email etc) y el objeto res (response) basicamente sirve para enviar la respuesta al cliente, next() unicamente se llama cuando de un middleware quieres brincar al siguiente, esto permite separar logica de la funcion principal, aqui les dejo un ejemplo de un middleware que valida si un usuario es adminfunction isAdmin(req, res, next) { // Si el usuario no es administrador, redirigir a la página de inicio if (!req.user || !req.user.isAdmin) { return res.redirect('/'); } // Si el usuario es administrador, continuar con la siguiente función middleware next();}
async function saveUser(req, res) { //Guardar el usuario en la base de datosawait db.saveUser(req.body) res.send('Usuario guardado');}
router.post('/saveUser', isAdmin, saveUser);
functionisAdmin(req, res, next){// Si el usuario no es administrador, redirigir a la página de inicioif(!req.user||!req.user.isAdmin){return res.redirect('/');}// Si el usuario es administrador, continuar con la siguiente función middlewarenext();}asyncfunctionsaveUser(req, res){//Guardar el usuario en la base de datosawait db.saveUser(req.body) res.send('Usuario guardado');}router.post('/saveUser', isAdmin, saveUser);```Cada que un usuario( o cualquier cliente que use HTTP) llame a /saveUser primero pasara por la funcion isAdmin, si no es admin no continua a guardar al usuario