Hace tres meses, un colega me preguntó qué framework debería usar para su nuevo proyecto. Mi respuesta fue terrible — le di la típica "depende de tu caso de uso" y lo dejé igual de perdido que antes. Así que escribo esto para hacer las cosas bien. He trabajado con los tres frameworks en producción en el último año, en proyectos distintos y con equipos de distinto tamaño, y tengo opiniones concretas.
Spoiler: no es una competencia reñida para todos los casos. Para algunos escenarios hay una respuesta bastante clara.
Por qué terminé usando los tres en el mismo año
Mi trabajo principal es en una startup de datos médicos (equipo de seis personas). Usamos Django. En paralelo, mantengo dos proyectos freelance: uno con Flask, otro con FastAPI. Esto no fue planeado — fue la acumulación de decisiones tomadas en distintos momentos con distintos contextos. Pero el resultado es que puedo comparar desde la trinchera, no desde benchmarks de blog.
El proyecto de Flask tiene cuatro años. FastAPI lo empecé en octubre pasado. Django lo heredé hace dieciocho meses cuando entré al equipo. No elegí los tres libremente desde cero — y eso importa, porque en el mundo real rara vez uno elige sin restricciones.
Flask: cuando la sencillez es la feature
Flask 3.1 sigue siendo Flask. Eso es bueno y malo al mismo tiempo.
Lo bueno: puedes levantar un servidor funcional en veinte líneas de código y entender cada una. No hay magia, no hay convenciones implícitas, no hay archivos de configuración misteriosos. Para un equipo pequeño o un proyecto donde necesitas control total sobre la arquitectura, eso vale mucho.
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
# Sin decoradores fancy, sin inyección de dependencias —
# simplemente Python normal que cualquiera puede leer
def require_api_key(f):
@wraps(f)
def decorated(*args, **kwargs):
key = request.headers.get("X-API-Key")
if key != app.config["API_KEY"]:
return jsonify({"error": "Unauthorized"}), 401
return f(*args, **kwargs)
return decorated
@app.route("/health")
def health():
return {"status": "ok"}
@app.route("/data", methods=["POST"])
@require_api_key
def receive_data():
payload = request.get_json()
# procesar...
return jsonify({"received": True}), 202
Este código lo escribí para un cliente hace tres años y sigue corriendo en producción sin cambios sustanciales. Esa es la realidad de Flask: estable, predecible, sin sorpresas.
Pero el problema — y esto me lo enseñó la experiencia — es que Flask no te da nada de forma gratuita. Validación de datos: tú la construyes. Documentación de API: tú la generas. Autenticación: elige tu extensión, espera que esté mantenida, reza. El ecosistema de extensiones de Flask es vasto pero fragmentado, y algunas partes están desactualizadas o abandonadas. Pasé una tarde entera en octubre intentando que flask-jwt-extended funcionara correctamente con las últimas versiones de dependencias, y terminé reescribiendo la lógica manualmente. No fue la mejor tarde de mi vida.
Flask tiene sentido cuando: tu equipo conoce Python bien y quiere construir la arquitectura ellos mismos, el proyecto es una API interna simple, o estás prototipando algo que puede crecer en cualquier dirección.
No tiene sentido cuando: necesitas un sistema completo con usuarios, permisos, admin, y no quieres construir todo desde cero.
Django: el elefante que funciona
Entré al proyecto de Django con cierto escepticismo. Tenía la idea — probablemente injusta — de que Django era para proyectos "de otra época", para cuando la gente hacía full-stack con templates HTML generados en servidor. Estaba equivocado, aunque no completamente.
Lo que me sorprendió fue qué tan sólido está el ORM y qué tan bien funciona Django REST Framework (DRF) para APIs modernas. El proyecto heredado tiene un modelo de datos con cuarenta y dos tablas relacionadas entre sí. Las migraciones de Django manejan eso con una fluidez que, honestamente, no esperaba. Hemos corrido más de trescientas migraciones en dieciocho meses sin un solo problema de integridad de datos — aunque reconozco que parte de ese crédito le pertenece al equipo anterior que diseñó bien el esquema.
El panel de administración es otra cosa que subestimé. Para una startup donde los no-técnicos necesitan acceso a datos, el admin de Django es — okay, déjame ser preciso aquí — no es hermoso, pero funciona. Y personalizarlo es mucho menos doloroso de lo que parece al principio.
Ahora, los problemas reales que encontré:
Primero: la curva de aprendizaje de DRF. No me malinterpreten, DRF es poderoso. Pero la abstracción de ViewSets, Routers, Serializers — todo junto puede resultar confuso cuando alguien nuevo entra al equipo. Incorporar a un dev junior en enero nos tomó casi dos semanas solo para que entendiera el patrón completo de una sola vista. Eso es tiempo caro.
Segundo: rendimiento asíncrono. Django 4.2+ tiene soporte async, y Django 5.x lo mejoró significativamente. Pero no es async-first — es async-añadido. Para endpoints donde hago múltiples llamadas a APIs externas en paralelo, el código async en Django se siente más forzado que natural. No imposible. Solo... incómodo.
Tercero — y este fue mi momento "empujé esto un viernes por la tarde y lo pagué caro": instalé una nueva extensión de Django para un feature de reportes, no la probé lo suficiente en staging, y rompió el endpoint de autenticación en producción a las 6pm. No fue la extensión en sí — fue una incompatibilidad con nuestra versión de djangorestframework-simplejwt. Dos horas de rollback y debugging. La lección: el ecosistema de Django es grande, pero las incompatibilidades entre paquetes son más frecuentes de lo que uno espera.
Si necesitas un sistema completo — usuarios, roles, admin, un ORM que gestione cuarenta tablas sin que nadie se vuelva loco — Django es donde yo apostaría. La convención sobre configuración tiene un precio en flexibilidad, pero el tiempo que ahorras en las primeras semanas lo justifica. Y si el equipo va a crecer, la estructura predecible de Django tiene un valor que subestimé hasta que lo viví.
FastAPI: lo que cambió mi forma de pensar sobre APIs
Empecé el proyecto de FastAPI en octubre sin grandes expectativas. Era un servicio de inferencia para un modelo de machine learning — recibe una imagen, devuelve una clasificación — y necesitaba algo rápido de construir y con soporte async nativo para manejar las llamadas al modelo sin bloquear.
La primera semana fue reveladora en un sentido que no anticipé: la validación automática con Pydantic y la generación automática de documentación OpenAPI cambiaron fundamentalmente cómo pienso sobre el contrato de una API.
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, Field
import httpx
app = FastAPI(title="Inference API", version="0.3.1")
class ImageRequest(BaseModel):
url: str = Field(..., description="URL de la imagen a clasificar")
confidence_threshold: float = Field(0.7, ge=0.0, le=1.0)
# Pydantic valida esto automáticamente — si mandan 1.5, reciben un 422 claro
# No tengo que escribir un solo if/else de validación
class ClassificationResult(BaseModel):
label: str
confidence: float
processing_time_ms: int
async def get_http_client():
async with httpx.AsyncClient(timeout=10.0) as client:
yield client
@app.post("/classify", response_model=ClassificationResult)
async def classify_image(
req: ImageRequest,
client: httpx.AsyncClient = Depends(get_http_client)
):
# El tipo de retorno está documentado automáticamente en /docs
# Y FastAPI valida que mi respuesta también sea correcta
try:
result = await model_service.classify(req.url, client)
return ClassificationResult(**result)
except TimeoutError:
raise HTTPException(status_code=504, detail="Model service timeout")
La inyección de dependencias de FastAPI es, para mí, uno de sus grandes aciertos. El patrón Depends() parece simple al principio, pero cuando empiezas a componer dependencias — un cliente HTTP que depende de una configuración que depende del entorno — de repente entiendes por qué FastAPI lo diseñó así. Mi código de tests también se simplificó enormemente porque puedo sobreescribir dependencias fácilmente.
Lo que no me esperaba: el rendimiento. No es que Flask o Django sean lentos para la mayoría de casos — pero cuando empecé a medir tiempos en el servicio de ML, la diferencia fue notable. En condiciones de carga moderada (cien requests concurrentes), el endpoint async de FastAPI terminó manejando la carga con una latencia media de 45ms versus la versión equivalente en Flask que daba 180ms. Ese no es un número de benchmark artificial — es de Locust corriendo en staging.
Lo que sí me frustró: el ecosistema de autenticación todavía no es tan maduro como Django o incluso Flask. fastapi-users funciona bien, pero cuando necesité algo ligeramente fuera del flujo estándar — un sistema de invitaciones para onboarding de usuarios empresariales — tuve que escribir bastante lógica custom. No es un dealbreaker, pero es trabajo real que hay que presupuestar.
También: la documentación de FastAPI es excelente en los casos básicos y se vuelve escasa en los casos avanzados. Los docs oficiales te llevan hasta cierto punto y después estás en Stack Overflow y GitHub issues. En diciembre tuve un problema con WebSocket + autenticación por cookie que me tomó un día y medio resolver — y la solución estaba en un issue de GitHub de 2023 con cuarenta comentarios. No estoy del todo seguro de que ese patrón escale bien más allá de mi caso de uso, pero funcionó.
FastAPI tiene sentido cuando: construyes una API que va a ser consumida por otros desarrolladores, necesitas async nativo, tienes un componente de ML o IO-intensivo, o quieres que la documentación se genere sola desde el código.
El momento en que paré y pensé: ¿qué estoy recomendando realmente?
Hace un mes hice este mismo análisis mentalmente para dar feedback en una empresa amiga. Son cinco personas, construyendo una herramienta B2B de análisis de datos para el sector retail. Necesitan usuarios, permisos por rol, un dashboard para que sus clientes vean reportes, y una API para integraciones.
Mi respuesta, directa: Django.
No porque sea el más moderno. No porque tenga el mejor rendimiento. Sino porque el día uno tendrían un admin funcional para gestionar clientes, el día dos podrían tener autenticación, y el día diez podrían estar construyendo features reales. El costo de aprendizaje inicial vale la velocidad de iteración que se gana en las semanas dos a veinte.
Si la misma empresa me dijera "en realidad, el dashboard lo construye el frontend en React, nosotros solo necesitamos una API", el análisis cambia. Ahí Flask o FastAPI entran en juego dependiendo de si el perfil del equipo es más cómodo con estructura mínima (Flask) o quieren validación y docs automáticas (FastAPI).
Y si el contexto es un servicio de ML en producción, o una API de alta concurrencia donde las llamadas a servicios externos son la norma — FastAPI es donde yo apostaría hoy.
Lo que yo haría en 2026
Mi guía, sin rodeos:
Usa Django si construyes un producto con usuarios, roles, contenido, y el equipo va a crecer. La batería incluida es real y ahorra semanas. Las migraciones son confiables. El admin es suficientemente bueno para el 80% de las necesidades internas.
Usa FastAPI si construyes servicios — APIs puras, microservicios, endpoints de ML, integraciones. El tipado fuerte con Pydantic previene errores que en Flask o Django solo aparecen en producción. La documentación automática es genuinamente útil cuando hay clientes externos consumiendo tu API.
Usa Flask si el proyecto es pequeño, el equipo lo conoce bien, o la flexibilidad total es realmente necesaria. No lo recomendaría para proyectos nuevos donde no hay una razón específica — la ventaja de "simplicidad" se erosiona rápido cuando necesitas añadir la décima extensión.
Dicho todo esto: si ya tienes código en producción funcionando, el costo de migrar normalmente no vale la pena. El framework correcto es frecuentemente el que ya está ahí.
Anyway — si tu colega te pregunta qué usar, ahora tienes algo más concreto que "depende". Espero que ayude.
Top comments (0)