Middleware Avanzado en Django 2026: Middleware Personalizado, Logging y Preguntas de Entrevista
Guía completa sobre middleware avanzado en Django 2026. Creación de middleware personalizado, logging estructurado, manejo de errores y preguntas de entrevista técnica para desarrolladores Python.

El sistema de middleware representa una de las arquitecturas más poderosas de Django, permitiendo interceptar y procesar cada solicitud y respuesta HTTP que atraviesa la aplicación. En 2026, con Django 6.0 y las mejoras en el sistema asíncrono, dominar los middleware se vuelve esencial para cualquier desarrollador backend serio. Este artículo explora la creación de middleware personalizado, estrategias avanzadas de logging y las preguntas más frecuentes en entrevistas técnicas.
Cada middleware de Django funciona como una capa en una cebolla: las solicitudes atraviesan los middleware de afuera hacia adentro (de arriba hacia abajo en MIDDLEWARE), mientras que las respuestas siguen el camino inverso. Esta arquitectura permite agregar funcionalidades transversales como autenticación, logging o gestión de caché sin modificar el código de las vistas.
Arquitectura de Middleware en Django: Funcionamiento Interno
El sistema de middleware de Django se basa en el patrón de cadena de responsabilidad. Cada middleware recibe una solicitud entrante o una respuesta saliente, y puede elegir procesarla, modificarla o pasarla al siguiente middleware. La configuración se realiza en el archivo settings.py mediante la lista MIDDLEWARE.
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# Middleware personalizados
'myapp.middleware.RequestLoggingMiddleware',
'myapp.middleware.RateLimitMiddleware',
]El orden de los middleware es crucial. SecurityMiddleware debe estar primero para agregar los encabezados de seguridad, mientras que SessionMiddleware debe preceder a AuthenticationMiddleware porque la autenticación depende de las sesiones. Un posicionamiento incorrecto puede causar comportamientos impredecibles o vulnerabilidades de seguridad.
Creación de Middleware Personalizado: Sintaxis Moderna
Django soporta dos estilos de middleware: la sintaxis basada en clases (recomendada desde Django 2.0) y la antigua sintaxis basada en hooks. La sintaxis moderna utiliza una clase callable con un método call que encapsula el procesamiento.
# myapp/middleware.py
import time
import logging
logger = logging.getLogger(__name__)
class RequestTimingMiddleware:
"""Middleware que mide el tiempo de procesamiento de cada solicitud."""
def __init__(self, get_response):
self.get_response = get_response
# Configuración única al inicio del servidor
def __call__(self, request):
# Código ejecutado ANTES de la vista (solicitud entrante)
start_time = time.perf_counter()
# Llamada al siguiente middleware o a la vista
response = self.get_response(request)
# Código ejecutado DESPUÉS de la vista (respuesta saliente)
duration = time.perf_counter() - start_time
response['X-Request-Duration'] = f'{duration:.4f}s'
logger.info(
'Request processed',
extra={
'path': request.path,
'method': request.method,
'duration_ms': duration * 1000,
'status_code': response.status_code,
}
)
return responseEsta estructura garantiza que el código antes de get_response() se ejecute durante la fase de solicitud, mientras que el código después se ejecuta durante la fase de respuesta. El parámetro get_response representa el siguiente elemento en la cadena, ya sea otro middleware o la vista final.
Middleware Asíncrono con Django 6.0
Django 6.0 refuerza el soporte nativo de middleware asíncronos, permitiendo operaciones no bloqueantes como llamadas a servicios externos o consultas de base de datos asíncronas.
# myapp/middleware.py
import asyncio
from asgiref.sync import iscoroutinefunction
class AsyncAuthorizationMiddleware:
"""Middleware asíncrono que verifica permisos externos."""
async_capable = True
sync_capable = False
def __init__(self, get_response):
self.get_response = get_response
if iscoroutinefunction(self.get_response):
self._is_coroutine = asyncio.coroutines._is_coroutine
async def __call__(self, request):
# Verificación asíncrona de permisos
if hasattr(request, 'user') and request.user.is_authenticated:
permissions = await self.fetch_external_permissions(
request.user.id
)
request.user_permissions = permissions
response = await self.get_response(request)
return response
async def fetch_external_permissions(self, user_id: int) -> list:
"""Llamada asíncrona a un servicio de permisos externo."""
# Simulación de una llamada API externa
await asyncio.sleep(0.01) # Reemplazar con httpx o aiohttp
return ['read', 'write', 'delete']Los atributos async_capable y sync_capable indican a Django los modos soportados por el middleware. Un middleware marcado con sync_capable=False solo se utilizará en un contexto ASGI con un servidor como Uvicorn o Daphne.
Logging Estructurado con structlog y JSON
El logging tradicional de Django produce mensajes de texto difíciles de parsear por los sistemas de monitoreo modernos. La adopción del logging estructurado en JSON facilita la integración con herramientas como Elasticsearch, Datadog o Grafana Loki.
# settings.py
import structlog
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'json': {
'()': structlog.stdlib.ProcessorFormatter,
'processor': structlog.processors.JSONRenderer(),
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'json',
},
},
'loggers': {
'': {
'handlers': ['console'],
'level': 'INFO',
},
'django.request': {
'handlers': ['console'],
'level': 'INFO',
'propagate': False,
},
},
}
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt='iso'),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)Esta configuración produce logs JSON que contienen automáticamente el timestamp, el nivel de log y los metadatos estructurados, facilitando las consultas y alertas en las plataformas de monitoreo.
Middleware de Logging de Solicitudes con Correlation ID
Para rastrear una solicitud a través de múltiples servicios en una arquitectura de microservicios, el uso de un correlation ID (o request ID) es indispensable. Este middleware genera o propaga un identificador único para cada solicitud.
# myapp/middleware.py
import uuid
import structlog
from django.http import HttpRequest, HttpResponse
logger = structlog.get_logger(__name__)
class CorrelationIdMiddleware:
"""Agrega un correlation ID a cada solicitud para trazabilidad distribuida."""
HEADER_NAME = 'X-Correlation-ID'
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request: HttpRequest) -> HttpResponse:
# Recuperar o generar el correlation ID
correlation_id = request.headers.get(
self.HEADER_NAME,
str(uuid.uuid4())
)
# Adjuntar a la solicitud para uso en las vistas
request.correlation_id = correlation_id
# Configurar structlog con el contexto
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(
correlation_id=correlation_id,
path=request.path,
method=request.method,
)
logger.info('request_started')
response = self.get_response(request)
# Propagar el correlation ID en la respuesta
response[self.HEADER_NAME] = correlation_id
logger.info(
'request_completed',
status_code=response.status_code,
)
return responseEste patrón permite correlacionar los logs entre un frontend, una API Django, workers de Celery y servicios externos, facilitando considerablemente el debugging en producción.
Middleware de Rate Limiting con Redis
La limitación de tasa (rate limiting) protege la API contra abusos y ataques de denegación de servicio. Esta implementación utiliza Redis para un conteo distribuido compatible con despliegues multi-instancia.
# myapp/middleware.py
import redis
from django.http import JsonResponse
from django.conf import settings
class RateLimitMiddleware:
"""Limita el número de solicitudes por IP o usuario."""
def __init__(self, get_response):
self.get_response = get_response
self.redis = redis.Redis.from_url(settings.REDIS_URL)
self.rate_limit = 100 # Solicitudes por ventana
self.window_seconds = 60 # Ventana deslizante
def __call__(self, request):
client_id = self.get_client_identifier(request)
key = f'ratelimit:{client_id}'
# Incrementar el contador con expiración
pipe = self.redis.pipeline()
pipe.incr(key)
pipe.expire(key, self.window_seconds)
count, _ = pipe.execute()
if count > self.rate_limit:
return JsonResponse(
{
'error': 'Rate limit exceeded',
'retry_after': self.window_seconds,
},
status=429,
)
response = self.get_response(request)
# Agregar los encabezados de rate limiting
response['X-RateLimit-Limit'] = str(self.rate_limit)
response['X-RateLimit-Remaining'] = str(
max(0, self.rate_limit - count)
)
return response
def get_client_identifier(self, request) -> str:
"""Identifica al cliente por usuario autenticado o IP."""
if hasattr(request, 'user') and request.user.is_authenticated:
return f'user:{request.user.id}'
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
return f'ip:{x_forwarded_for.split(",")[0].strip()}'
return f'ip:{request.META.get("REMOTE_ADDR")}'Los encabezados X-RateLimit-* informan a los clientes sobre su cuota restante, permitiendo que las aplicaciones consumidoras adapten su comportamiento antes de alcanzar el límite.
Manejo de Excepciones con Middleware
Un middleware de manejo centralizado de excepciones captura los errores no manejados, los registra con contexto y retorna respuestas consistentes a los clientes.
# myapp/middleware.py
import traceback
import structlog
from django.http import JsonResponse
logger = structlog.get_logger(__name__)
class ExceptionHandlingMiddleware:
"""Captura y registra todas las excepciones no manejadas."""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
"""Hook llamado cuando una vista lanza una excepción."""
logger.error(
'unhandled_exception',
exception_type=type(exception).__name__,
exception_message=str(exception),
path=request.path,
method=request.method,
user_id=getattr(request.user, 'id', None),
traceback=traceback.format_exc(),
)
# Respuesta genérica en producción
return JsonResponse(
{
'error': 'Internal server error',
'correlation_id': getattr(
request, 'correlation_id', 'unknown'
),
},
status=500,
)El método process_exception es un hook especial que se llama únicamente cuando una vista lanza una excepción. Permite capturar el error antes de que suba por la pila de middleware.
¿Listo para aprobar tus entrevistas de Django?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Preguntas de Entrevista: Middleware Django
Las entrevistas técnicas para posiciones Django frecuentemente abordan los middleware. Estas son las preguntas más comunes con sus respuestas detalladas.
Pregunta: ¿Cuál es la diferencia entre call y process_view en un middleware?
El método call es el punto de entrada principal que encapsula todo el ciclo solicitud-respuesta. Se ejecuta para cada solicitud. El método process_view es un hook opcional que se llama justo antes de la ejecución de la vista, después de la resolución de la URL pero antes de llamar a la función de vista. Recibe la vista, sus argumentos posicionales y sus argumentos nombrados, permitiendo una inspección o modificación detallada.
Pregunta: ¿Cómo puede un middleware cortocircuitar la cadena de procesamiento?
Retornando directamente un objeto HttpResponse desde call en lugar de llamar a self.get_response(request), el middleware impide que los siguientes middleware y la vista se ejecuten. Esta técnica se usa para autenticación, rate limiting o validación.
def __call__(self, request):
if not self.is_authorized(request):
return HttpResponse('Unauthorized', status=401)
return self.get_response(request)Pregunta: ¿Cómo manejar middleware con dependencias circulares?
Las dependencias circulares entre middleware deben evitarse por diseño. Si un middleware A depende de datos agregados por B, y B depende de datos de A, se debe fusionar los middleware o crear un middleware intermedio que consolide las dependencias. El orden en MIDDLEWARE siempre debe formar un grafo acíclico.
Pregunta: ¿Cuándo usar un middleware vs un decorador de vista?
Los middleware se aplican globalmente a todas las solicitudes y son adecuados para funcionalidades transversales (logging, seguridad, sesiones). Los decoradores se aplican selectivamente a vistas específicas y son adecuados para validaciones locales (permisos específicos, caché condicional). Un middleware que verifica una condición y omite la mayoría de las solicitudes probablemente debería ser un decorador.
Pregunta: ¿Cómo probar un middleware de forma aislada?
Los middleware se prueban creando una instancia con un get_response mockeado, luego llamando al middleware con objetos RequestFactory.
from django.test import TestCase, RequestFactory
from myapp.middleware import RequestTimingMiddleware
class TestTimingMiddleware(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.get_response = lambda r: HttpResponse('OK')
self.middleware = RequestTimingMiddleware(self.get_response)
def test_adds_duration_header(self):
request = self.factory.get('/test/')
response = self.middleware(request)
self.assertIn('X-Request-Duration', response)Buenas Prácticas y Patrones Avanzados
El diseño de middleware performantes y mantenibles sigue varios principios establecidos. Cada middleware debe tener una responsabilidad única y bien definida. Un middleware que maneja tanto autenticación como logging debería dividirse en dos componentes distintos.
Las operaciones costosas (consultas de base de datos, llamadas de red) deben minimizarse o hacerse asíncronas. Un middleware síncrono que realiza llamadas HTTP bloqueantes degradará significativamente el rendimiento bajo carga.
La configuración debe externalizarse en los settings de Django en lugar de estar hardcodeada. Esto facilita el despliegue en diferentes entornos sin modificación del código.
class ConfigurableMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.enabled = getattr(settings, 'MY_MIDDLEWARE_ENABLED', True)
self.threshold = getattr(settings, 'MY_MIDDLEWARE_THRESHOLD', 100)Conclusión
Dominar los middleware de Django abre el camino a arquitecturas de aplicación robustas y observables. La creación de middleware personalizados para logging estructurado, rate limiting y manejo de errores constituye una habilidad fundamental para los desarrolladores Python que trabajan en aplicaciones de producción. Los conceptos abordados, desde el correlation ID hasta el logging JSON pasando por el rate limiting distribuido, representan el estado del arte de las prácticas Django en 2026. Estos conocimientos, combinados con la comprensión profunda del ciclo solicitud-respuesta, preparan eficazmente para las entrevistas técnicas de posiciones backend exigentes.
¿Sabrías detectar el bug en Django?
Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Escrito por
Anthony Fillion-MailletFundador de SharpSkill
Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.
Actualizado el 18 de septiembre de 2026
Compartir
Artículos relacionados

Personalización del Admin de Django en 2026: Acciones, Filtros y Preguntas de Entrevista
Guía completa sobre la personalización de la interfaz de administración de Django en 2026. Acciones personalizadas, filtros avanzados y preguntas técnicas de entrevista para desarrolladores Django.

Django Signals vs Celery Tasks en 2026: Cuándo Usar Cada Uno y Preguntas de Entrevista
Aprende cuándo usar señales Django versus tareas Celery. Cubre el manejo de eventos síncrono vs asíncrono, implicaciones de rendimiento y preguntas de entrevista para desarrolladores Django.

Django Channels en 2026: WebSockets, Tiempo Real y Preguntas de Entrevista
Guía completa sobre Django Channels 4.x para WebSockets y comunicación en tiempo real. Configuración de Redis, consumers async y preguntas de entrevista técnica.