GUÍA COMPLETA PARA CREAR MIDDLEWARE EN REDUX DESDE CERO
Introducción a Redux Middleware
Redux es una biblioteca de JavaScript ampliamente utilizada para gestionar el estado de aplicaciones de manera predecible. Su diseño, inspirado en la arquitectura Flux y la programación funcional, permite que los cambios en el estado sean transparentes y consistentes. Sin embargo, en aplicaciones complejas, la necesidad de manejar acciones asíncronas en Redux, como peticiones a APIs o registro de eventos, requiere herramientas adicionales. Aquí es donde entra en juego el middleware de Redux, un mecanismo poderoso que extiende las capacidades del despacho de acciones. Este tutorial explica qué es el middleware, cómo funciona y cómo crear uno desde cero, con ejemplos prácticos para desarrolladores que buscan dominar esta tecnología en 2025.
El middleware actúa como un punto de extensión entre el despacho de una acción y el momento en que esta llega al reductor. Permite interceptar acciones, modificarlas, registrarlas o incluso realizar operaciones asíncronas antes de que el estado se actualice. Este enfoque es ideal para tareas como el manejo de errores, la integración con APIs o la implementación de enrutamiento. A lo largo de este artículo, exploraremos los conceptos fundamentales, la estructura de un middleware y cómo implementarlo en una aplicación moderna, utilizando tanto Redux clásico como Redux Toolkit, la herramienta recomendada para simplificar la gestión del estado.
¿Qué es un Middleware en Redux?
Un middleware en Redux es una función que intercepta las acciones despachadas antes de que lleguen al reductor. Proporciona un punto intermedio donde los desarrolladores pueden agregar lógica personalizada, como registrar acciones, manejar efectos secundarios o realizar operaciones asíncronas. A diferencia de los reductores, que son funciones puras y no deben contener efectos secundarios, el middleware es el lugar ideal para gestionar estas operaciones.
En términos simples, el middleware envuelve el método dispatch del store, permitiendo que las acciones pasen por una cadena de transformaciones antes de llegar al reductor. Por ejemplo, un middleware puede registrar cada acción en la consola, realizar una llamada a una API o incluso cancelar una acción si no cumple con ciertas condiciones. Esta flexibilidad hace que los middlewares sean esenciales para aplicaciones que requieren lógica empresarial en Redux.
// Ejemplo de un middleware simple que registra acciones
const loggerMiddleware =
({ getState }) =>
(next) =>
(action) => {
console.log("Acción despachada:", action);
console.log("Estado antes:", getState());
const result = next(action);
console.log("Estado después:", getState());
return result;
};
En este código, el middleware loggerMiddleware imprime la acción despachada y el estado antes y después de que la acción se procese. La función next pasa la acción al siguiente middleware en la cadena o al reductor si no hay más middlewares.
Estructura de un Middleware
Un middleware en Redux sigue una estructura específica basada en funciones de orden superior. Esta estructura puede parecer compleja al principio, pero es fácil de entender una vez que se descompone en sus partes:
- Capa externa: Recibe el objeto
{ getState, dispatch }del store, proporcionando acceso al estado actual y al método de despacho. - Capa intermedia: Recibe la función
next, que representa el siguiente middleware en la cadena o el métododispatchfinal. - Capa interna: Recibe la
actiondespachada y ejecuta la lógica personalizada antes de llamar anext(action).
La firma completa de un middleware es:
const middleware =
({ getState, dispatch }) =>
(next) =>
(action) => {
// Lógica personalizada
return next(action);
};
Este diseño permite que los middlewares sean combinables y reutilizables. Por ejemplo, puedes tener un middleware para registrar acciones, otro para manejar errores y otro para realizar peticiones asíncronas, todos trabajando en conjunto sin interferir entre sí.
Creando un Middleware desde Cero
Para ilustrar cómo crear un middleware, implementaremos uno que detecte palabras prohibidas en el título de un artículo antes de que se agregue al estado. Este middleware verificará la carga útil de la acción y despachará una acción alternativa si encuentra una palabra no permitida.
Supongamos que tenemos una aplicación de blog donde los usuarios pueden agregar artículos. Queremos evitar que los títulos contengan palabras específicas, como “spam” o “error”. El middleware inspeccionará la acción y, si el título contiene una palabra prohibida, despachará una acción de advertencia en lugar de permitir que el artículo se agregue.
const forbiddenWords = ["spam", "error"];
const forbiddenWordsMiddleware =
({ dispatch }) =>
(next) =>
(action) => {
if (action.type === "ADD_ARTICLE") {
const title = action.payload.title.toLowerCase();
const foundWord = forbiddenWords.find((word) =>
title.includes(word)
);
if (foundWord) {
return dispatch({
type: "FOUND_FORBIDDEN_WORD",
payload: foundWord,
});
}
}
return next(action);
};
En este ejemplo, el middleware forbiddenWordsMiddleware verifica si la acción es de tipo ADD_ARTICLE. Si el título contiene una palabra prohibida, despacha una acción FOUND_FORBIDDEN_WORD con la palabra encontrada como carga útil. De lo contrario, pasa la acción al siguiente middleware o al reductor.
Para usar este middleware, necesitamos integrarlo en el store de Redux. Aquí está cómo configurarlo:
import { createStore, applyMiddleware } from "redux";
import rootReducer from "./reducers";
const store = createStore(
rootReducer,
applyMiddleware(forbiddenWordsMiddleware)
);
export default store;
Este código crea un store con nuestro middleware personalizado. Ahora, cada vez que se despache una acción ADD_ARTICLE, el middleware verificará el título antes de que la acción llegue al reductor.
Manejo de Acciones Asíncronas con Middleware
Uno de los usos más comunes del middleware es manejar acciones asíncronas en Redux, como peticiones a APIs. Redux por sí solo no soporta acciones asíncronas, ya que los reductores esperan acciones síncronas que sean objetos simples. Los middlewares como redux-thunk o redux-saga resuelven este problema, pero también puedes crear un middleware personalizado para manejar estas operaciones.
Por ejemplo, creemos un middleware que realice una llamada a una API para obtener datos y despache una acción con el resultado:
const apiMiddleware =
({ dispatch }) =>
(next) =>
(action) => {
if (action.type === "FETCH_DATA") {
fetch("https://api.example.com/data")
.then((response) => response.json())
.then((data) => {
dispatch({ type: "FETCH_DATA_SUCCESS", payload: data });
})
.catch((error) => {
dispatch({
type: "FETCH_DATA_ERROR",
payload: error.message,
});
});
return;
}
return next(action);
};
Este middleware intercepta acciones de tipo FETCH_DATA, realiza una petición a una API y despacha acciones de éxito o error según el resultado. Para usarlo, lo añadimos al store de la misma manera que el middleware anterior:
import { createStore, applyMiddleware } from "redux";
import rootReducer from "./reducers";
const store = createStore(rootReducer, applyMiddleware(apiMiddleware));
export default store;
Simplificando con Redux Toolkit
En 2025, Redux Toolkit es la forma recomendada de trabajar con Redux debido a su capacidad para reducir el código repetitivo y simplificar la configuración. Redux Toolkit incluye utilidades como configureStore, que configura automáticamente el store con middlewares predeterminados, como redux-thunk, y habilita la integración con Redux DevTools.
Para usar nuestro middleware personalizado con Redux Toolkit, podemos integrarlo en configureStore:
import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "./reducers";
import { forbiddenWordsMiddleware } from "./middleware";
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(forbiddenWordsMiddleware),
});
export default store;
En este ejemplo, usamos getDefaultMiddleware para incluir los middlewares predeterminados de Redux Toolkit y añadimos nuestro forbiddenWordsMiddleware usando el método concat. Esto asegura que nuestro middleware personalizado funcione junto con los middlewares predeterminados, como redux-thunk.
Combinando Múltiples Middlewares
Redux permite combinar múltiples middlewares para manejar diferentes aspectos de la lógica de la aplicación. Por ejemplo, podemos combinar nuestro loggerMiddleware, forbiddenWordsMiddleware y apiMiddleware para registrar acciones, filtrar palabras prohibidas y manejar peticiones asíncronas, respectivamente.
import { createStore, applyMiddleware } from "redux";
import rootReducer from "./reducers";
import { loggerMiddleware } from "./middleware/logger";
import { forbiddenWordsMiddleware } from "./middleware/forbiddenWords";
import { apiMiddleware } from "./middleware/api";
const store = createStore(
rootReducer,
applyMiddleware(loggerMiddleware, forbiddenWordsMiddleware, apiMiddleware)
);
export default store;
El orden de los middlewares es importante, ya que las acciones pasan por ellos en secuencia. En este caso, las acciones primero son registradas por loggerMiddleware, luego verificadas por forbiddenWordsMiddleware y finalmente procesadas por apiMiddleware si son de tipo FETCH_DATA.
Con Redux Toolkit, la combinación es aún más sencilla:
import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "./reducers";
import { loggerMiddleware } from "./middleware/logger";
import { forbiddenWordsMiddleware } from "./middleware/forbiddenWords";
import { apiMiddleware } from "./middleware/api";
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(
loggerMiddleware,
forbiddenWordsMiddleware,
apiMiddleware
),
});
export default store;
Casos de Uso Prácticos
Los middlewares son extremadamente versátiles y se utilizan en una variedad de escenarios en aplicaciones modernas. Algunos casos de uso comunes incluyen:
- Registro de eventos: Un middleware puede registrar todas las acciones y estados para depuración o análisis. Esto es útil durante el desarrollo y en entornos de producción para rastrear errores.
- Manejo de errores: Un middleware puede capturar errores en las acciones asíncronas y despachar acciones de error para actualizar el estado de la aplicación.
- Autenticación: Un middleware puede verificar si un usuario está autenticado antes de permitir ciertas acciones, como acceder a recursos protegidos.
- Enrutamiento: Un middleware puede manejar cambios de ruta en la aplicación, actualizando el estado según la URL actual.
- Integración con APIs: Como vimos en el ejemplo de
apiMiddleware, los middlewares son ideales para gestionar peticiones asíncronas a APIs y actualizar el estado con los resultados.
Por ejemplo, un middleware para autenticación podría verse así:
const authMiddleware =
({ getState, dispatch }) =>
(next) =>
(action) => {
if (action.type === "ACCESS_PROTECTED_RESOURCE") {
const { user } = getState().auth;
if (!user.isAuthenticated) {
dispatch({
type: "AUTH_ERROR",
payload: "Usuario no autenticado",
});
return;
}
}
return next(action);
};
Este middleware verifica si el usuario está autenticado antes de permitir el acceso a un recurso protegido. Si no está autenticado, despacha una acción de error.
Mejores Prácticas para Crear Middlewares
Crear middlewares efectivos requiere seguir algunas mejores prácticas para garantizar que sean mantenibles y escalables:
-
Mantener la simplicidad: Cada middleware debe tener una responsabilidad única. Por ejemplo, separa el registro de eventos del manejo de APIs en middlewares distintos.
-
Evitar efectos secundarios innecesarios: Aunque los middlewares están diseñados para manejar efectos secundarios, asegúrate de que estos sean necesarios y estén bien controlados.
-
Usar Redux Toolkit cuando sea posible: Redux Toolkit reduce la complejidad de la configuración y mejora la experiencia de desarrollo con herramientas como
createSliceycreateAsyncThunk. -
Documentar la lógica: Dado que los middlewares contienen lógica empresarial, documenta claramente su propósito y comportamiento.
-
Probar los middlewares: Escribe pruebas unitarias para verificar que los middlewares manejen correctamente las acciones y los casos de error.
Por ejemplo, una prueba para el forbiddenWordsMiddleware podría verse así:
import { forbiddenWordsMiddleware } from "./middleware/forbiddenWords";
describe("forbiddenWordsMiddleware", () => {
it("should dispatch FOUND_FORBIDDEN_WORD for forbidden words", () => {
const dispatch = jest.fn();
const next = jest.fn();
const action = {
type: "ADD_ARTICLE",
payload: { title: "This is spam" },
};
forbiddenWordsMiddleware({ dispatch })(next)(action);
expect(dispatch).toHaveBeenCalledWith({
type: "FOUND_FORBIDDEN_WORD",
payload: "spam",
});
expect(next).not.toHaveBeenCalled();
});
it("should call next for valid titles", () => {
const dispatch = jest.fn();
const next = jest.fn();
const action = {
type: "ADD_ARTICLE",
payload: { title: "Valid title" },
};
forbiddenWordsMiddleware({ dispatch })(next)(action);
expect(next).toHaveBeenCalledWith(action);
expect(dispatch).not.toHaveBeenCalled();
});
});
Esta prueba verifica que el middleware despache la acción correcta cuando encuentra una palabra prohibida y pase la acción al siguiente middleware cuando el título es válido.
Integración con React
Aunque Redux puede usarse con cualquier biblioteca de JavaScript, su combinación con React es particularmente poderosa. La biblioteca react-redux proporciona herramientas como Provider, useSelector y useDispatch para integrar Redux en componentes de React. Cuando uses middlewares en una aplicación React, asegúrate de configurar el store correctamente y envolver tu aplicación con el componente Provider.
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import App from "./App";
import store from "./store";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
En un componente de React, puedes despachar acciones que serán interceptadas por tus middlewares:
import React from "react";
import { useDispatch } from "react-redux";
const ArticleForm = () => {
const dispatch = useDispatch();
const [title, setTitle] = React.useState("");
const handleSubmit = (e) => {
e.preventDefault();
dispatch({ type: "ADD_ARTICLE", payload: { title } });
setTitle("");
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Título del artículo"
/>
<button type="submit">Agregar</button>
</form>
);
};
export default ArticleForm;
En este ejemplo, el componente ArticleForm despacha una acción ADD_ARTICLE que será procesada por el forbiddenWordsMiddleware. Si el título contiene una palabra prohibida, el middleware despachará una acción de advertencia en lugar de agregar el artículo.
Conclusiones
Los middlewares de Redux son una herramienta esencial para extender la funcionalidad de las aplicaciones JavaScript, permitiendo manejar lógica compleja en Redux de manera modular y reusable. Desde el registro de eventos hasta la gestión de acciones asíncronas, los middlewares proporcionan un punto de extensión flexible que se adapta a las necesidades de aplicaciones modernas. Al crear middlewares desde cero, los desarrolladores pueden personalizar el flujo de datos, implementar lógica empresarial y mejorar la mantenibilidad del código.
Con la adopción de Redux Toolkit en 2025, la configuración de middlewares se ha simplificado, permitiendo a los desarrolladores centrarse en la lógica en lugar de en el código repetitivo. Al seguir las mejores prácticas, como mantener los middlewares simples y bien documentados, puedes construir aplicaciones escalables y fáciles de mantener. Los ejemplos prácticos presentados, como el middleware para palabras prohibidas y el manejo de APIs, demuestran cómo los middlewares pueden resolver problemas reales en el desarrollo frontend.
Dominar los middlewares de Redux no solo mejora tu capacidad para gestionar el estado, sino que también te prepara para enfrentar desafíos complejos en proyectos de gran escala. Experimenta con los ejemplos proporcionados, intégralos en tus aplicaciones y explora cómo los middlewares pueden transformar la arquitectura de tu código.