Si estás iniciando un nuevo proyecto web con Python, la pregunta es inevitable: ¿Flask o Django? Ambos son frameworks excelentes, maduros y con comunidades enormes, pero tienen filosofías radicalmente distintas. Flask te da libertad total con un enfoque minimalista, mientras que Django te entrega una solución completa con "baterías incluidas". En esta comparativa analizamos a fondo sus diferencias técnicas, ventajas, desventajas y cuándo usar cada uno, con ejemplos de código prácticos.
Filosofía: Microframework vs Full-Stack
La diferencia fundamental entre Flask y Django no es de capacidad — ambos pueden construir cualquier aplicación web. La diferencia está en su filosofía de diseño:
| Aspecto | Flask | Django |
|---|---|---|
| Tipo | Microframework | Full-stack framework |
| Filosofía | "Trae lo que necesites" | "Baterías incluidas" |
| Curva de aprendizaje | Baja (menos conceptos iniciales) | Media-alta (más estructura que aprender) |
| Estructura del proyecto | Libre (tú decides) | Definida por convención |
| ORM | No incluido (SQLAlchemy es lo común) | Incluido (Django ORM) |
| Admin | No incluido | Panel de administración automático |
| Autenticación | No incluida (Flask-Login, etc.) | Sistema completo incluido |
Tip: Una analogía útil: Flask es como comprar un terreno vacío y construir tu casa desde cero (máxima personalización). Django es como comprar una casa equipada y remodelada a tu gusto (arranque rápido, estructura sólida).
Hello World: Primeras Impresiones
Nada mejor que ver código para entender la diferencia. Veamos cómo se ve una aplicación mínima en cada framework:
Flask: Minimalismo puro
# app.py — Aplicación completa de Flask en un archivo
from flask import Flask
app = Flask(__name__)
@app.route('/')
def inicio():
return '¡Hola desde Flask!'
@app.route('/usuario/<nombre>')
def saludo(nombre):
return f'Hola, {nombre}!'
if __name__ == '__main__':
app.run(debug=True)
Con Flask, un solo archivo es suficiente para tener una aplicación funcional. No hay carpetas obligatorias, no hay configuración previa, no hay migraciones. Ejecutas python app.py y tienes un servidor corriendo.
Django: Estructura desde el inicio
# Crear proyecto Django
django-admin startproject mi_proyecto
cd mi_proyecto
python manage.py startapp principal
# Estructura resultante:
# mi_proyecto/
# ├── manage.py
# ├── mi_proyecto/
# │ ├── __init__.py
# │ ├── settings.py ← Configuración (~120 líneas)
# │ ├── urls.py ← Rutas principales
# │ ├── asgi.py
# │ └── wsgi.py
# └── principal/
# ├── __init__.py
# ├── admin.py ← Panel de admin
# ├── apps.py
# ├── models.py ← Modelos de BD
# ├── views.py ← Lógica de vistas
# ├── urls.py ← (lo creas tú)
# └── migrations/ ← Migraciones automáticas
# principal/views.py
from django.http import HttpResponse
def inicio(request):
return HttpResponse('¡Hola desde Django!')
def saludo(request, nombre):
return HttpResponse(f'Hola, {nombre}!')
# principal/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.inicio, name='inicio'),
path('usuario/<str:nombre>/', views.saludo, name='saludo'),
]
# mi_proyecto/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('principal.urls')),
]
Django requiere más archivos desde el inicio, pero esa estructura escala mejor cuando el proyecto crece. Además, ya tienes un panel de admin funcional en /admin/ sin escribir una sola línea extra.
Modelos y Base de Datos
El manejo de la base de datos es donde las diferencias se hacen más evidentes.
Django ORM: Integrado y poderoso
# models.py — Django
from django.db import models
class Categoria(models.Model):
nombre = models.CharField(max_length=100, unique=True)
slug = models.SlugField(unique=True)
class Meta:
verbose_name_plural = "categorías"
def __str__(self):
return self.nombre
class Producto(models.Model):
nombre = models.CharField(max_length=200)
precio = models.DecimalField(max_digits=10, decimal_places=2)
descripcion = models.TextField(blank=True)
categoria = models.ForeignKey(Categoria, on_delete=models.CASCADE, related_name='productos')
activo = models.BooleanField(default=True)
creado = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-creado']
def __str__(self):
return self.nombre
# Uso en views o shell:
# Crear
producto = Producto.objects.create(
nombre="Laptop Pro", precio=25999.00, categoria=cat
)
# Consultar
activos = Producto.objects.filter(activo=True, precio__lte=30000)
por_categoria = Producto.objects.filter(categoria__nombre="Electrónica")
# Agregar: las migraciones son automáticas
# python manage.py makemigrations
# python manage.py migrate
Flask + SQLAlchemy: Flexible pero manual
# models.py — Flask con SQLAlchemy
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
db = SQLAlchemy()
class Categoria(db.Model):
id = db.Column(db.Integer, primary_key=True)
nombre = db.Column(db.String(100), unique=True, nullable=False)
slug = db.Column(db.String(120), unique=True, nullable=False)
productos = db.relationship('Producto', backref='categoria', lazy=True)
def __repr__(self):
return f'<Categoria {self.nombre}>'
class Producto(db.Model):
id = db.Column(db.Integer, primary_key=True)
nombre = db.Column(db.String(200), nullable=False)
precio = db.Column(db.Numeric(10, 2), nullable=False)
descripcion = db.Column(db.Text, default='')
categoria_id = db.Column(db.Integer, db.ForeignKey('categoria.id'), nullable=False)
activo = db.Column(db.Boolean, default=True)
creado = db.Column(db.DateTime, default=datetime.utcnow)
# app.py — Configuración
from flask import Flask
from models import db
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://user:pass@localhost/mi_db'
db.init_app(app)
# Para migraciones necesitas Flask-Migrate (extra):
# flask db init
# flask db migrate -m "crear modelos"
# flask db upgrade
Con Django, las migraciones, las relaciones y el admin son automáticos. Con Flask, necesitas instalar y configurar SQLAlchemy + Flask-Migrate manualmente. La ventaja de Flask es que puedes elegir cualquier ORM (o ninguno), mientras que Django te compromete con el suyo.
APIs REST
Crear APIs es uno de los casos de uso más comunes. Veamos cómo se comparan:
Django REST Framework
# serializers.py
from rest_framework import serializers
from .models import Producto
class ProductoSerializer(serializers.ModelSerializer):
categoria_nombre = serializers.CharField(source='categoria.nombre', read_only=True)
class Meta:
model = Producto
fields = ['id', 'nombre', 'precio', 'descripcion', 'categoria',
'categoria_nombre', 'activo', 'creado']
read_only_fields = ['creado']
# views.py
from rest_framework import viewsets, filters
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .models import Producto
from .serializers import ProductoSerializer
class ProductoViewSet(viewsets.ModelViewSet):
queryset = Producto.objects.select_related('categoria').filter(activo=True)
serializer_class = ProductoSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ['nombre', 'descripcion']
ordering_fields = ['precio', 'creado']
# urls.py
from rest_framework.routers import DefaultRouter
from .views import ProductoViewSet
router = DefaultRouter()
router.register(r'productos', ProductoViewSet)
# Esto genera automáticamente:
# GET /api/productos/ → listar
# POST /api/productos/ → crear
# GET /api/productos/{id}/ → detalle
# PUT /api/productos/{id}/ → actualizar
# DELETE /api/productos/{id}/ → eliminar
Flask con flask-restful
# api.py — Flask
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://user:pass@localhost/mi_db'
db = SQLAlchemy(app)
# (asumiendo que el modelo Producto ya está definido)
@app.route('/api/productos', methods=['GET'])
def listar_productos():
productos = Producto.query.filter_by(activo=True).all()
return jsonify([{
'id': p.id,
'nombre': p.nombre,
'precio': float(p.precio),
'categoria': p.categoria.nombre
} for p in productos])
@app.route('/api/productos', methods=['POST'])
def crear_producto():
data = request.get_json()
producto = Producto(
nombre=data['nombre'],
precio=data['precio'],
categoria_id=data['categoria_id']
)
db.session.add(producto)
db.session.commit()
return jsonify({'id': producto.id, 'nombre': producto.nombre}), 201
@app.route('/api/productos/<int:id>', methods=['GET'])
def detalle_producto(id):
producto = Producto.query.get_or_404(id)
return jsonify({
'id': producto.id,
'nombre': producto.nombre,
'precio': float(producto.precio)
})
@app.route('/api/productos/<int:id>', methods=['DELETE'])
def eliminar_producto(id):
producto = Producto.query.get_or_404(id)
db.session.delete(producto)
db.session.commit()
return '', 204
Con Django REST Framework (DRF), un ModelViewSet de 10 líneas te da CRUD completo con serialización, validación, permisos, filtros, paginación y documentación automática. Con Flask, escribes cada endpoint manualmente — más control, pero más código.
Autenticación y Seguridad
| Característica | Django | Flask |
|---|---|---|
| Sistema de usuarios | Incluido (User model, groups, permissions) | Flask-Login + Flask-Security (extensiones) |
| Protección CSRF | Incluida y activada por defecto | Flask-WTF (extensión) |
| Hashing de contraseñas | PBKDF2 por defecto, configurable | Werkzeug o passlib (manual) |
| Sesiones | Incluidas (BD, caché, archivo) | Flask-Session (extensión para server-side) |
| Permisos por objeto | Sistema completo de permisos incluido | Flask-Principal o manual |
| Rate limiting | django-ratelimit (extensión) | Flask-Limiter (extensión) |
| Headers de seguridad | SecurityMiddleware incluido | Flask-Talisman (extensión) |
Django gana por mucho en seguridad out of the box. No significa que Flask sea inseguro — pero necesitas saber qué extensiones instalar y configurar. Con Django, las protecciones están activadas por defecto.
Panel de Administración
Esta es una de las ventajas más contundentes de Django. Con solo registrar tus modelos, obtienes un panel de administración completo:
# admin.py — Django
from django.contrib import admin
from .models import Producto, Categoria
@admin.register(Producto)
class ProductoAdmin(admin.ModelAdmin):
list_display = ['nombre', 'precio', 'categoria', 'activo', 'creado']
list_filter = ['categoria', 'activo']
search_fields = ['nombre', 'descripcion']
list_editable = ['precio', 'activo']
date_hierarchy = 'creado'
@admin.register(Categoria)
class CategoriaAdmin(admin.ModelAdmin):
list_display = ['nombre', 'slug']
prepopulated_fields = {'slug': ('nombre',)}
Con estas pocas líneas tienes un CRUD visual completo con búsqueda, filtros, edición en línea, paginación y permisos por usuario. En Flask, construir algo equivalente requiere usar Flask-Admin o desarrollarlo desde cero.
Rendimiento y Escalabilidad
| Métrica | Flask | Django |
|---|---|---|
| Requests/segundo (benchmark) | ~15,000 (Werkzeug) | ~8,000-12,000 (depende del middleware) |
| Latencia mínima | Menor (menos middleware) | Ligeramente mayor (más capas) |
| Uso de memoria | Menor (footprint reducido) | Mayor (más componentes cargados) |
| Async/Await | Sí (con Quart o async views) | Sí (ASGI desde Django 3.1+) |
| Caché | Flask-Caching (extensión) | Framework de caché incluido (memcached, Redis) |
En benchmarks puros, Flask es ligeramente más rápido porque tiene menos overhead. Pero en la práctica, la diferencia es insignificante — el cuello de botella casi siempre está en la base de datos, no en el framework. Ambos escalan horizontalmente sin problemas con Gunicorn, uWSGI o Uvicorn detrás de Nginx.
Tip: Instagram, Spotify (backend), y Mozilla usan Django en producción con millones de usuarios. Pinterest, Netflix (herramientas internas), y LinkedIn usan Flask. El rendimiento del framework no es un factor limitante en proyectos reales.
Ecosistema y Extensiones
Extensiones populares de Flask
flask-sqlalchemy → ORM (SQLAlchemy)
flask-migrate → Migraciones de BD
flask-login → Autenticación de usuarios
flask-wtf → Formularios con protección CSRF
flask-restful → APIs REST
flask-cors → Cross-Origin Resource Sharing
flask-mail → Envío de emails
flask-caching → Sistema de caché
flask-socketio → WebSockets
flask-admin → Panel de administración
Paquetes populares de Django
djangorestframework → APIs REST (el estándar)
django-allauth → Auth social (Google, GitHub, etc.)
django-cors-headers → Cross-Origin Resource Sharing
django-filter → Filtros avanzados para querysets
django-debug-toolbar → Debugging en desarrollo
celery → Tareas asíncronas
django-storages → Almacenamiento S3, GCS, Azure
django-import-export → Import/export CSV, Excel
whitenoise → Servir archivos estáticos
django-extensions → Utilidades extra (shell_plus, etc.)
Django tiene un ecosistema más cohesivo — los paquetes se integran naturalmente porque todos asumen la misma estructura. Flask tiene un ecosistema más diverso — más opciones pero requiere más trabajo de integración.
¿Cuándo Elegir Flask?
Flask es la mejor opción cuando:
- Microservicios: Servicios pequeños y enfocados que hacen una sola cosa bien
- APIs simples: Endpoints REST o GraphQL sin necesidad de admin o templates
- Prototipos rápidos: Validar una idea en minutos con un solo archivo
- Aprendizaje: Entender cómo funciona un framework web desde las bases
- Máximo control: Quieres elegir cada componente (ORM, template engine, etc.)
- Integración con ML/IA: Servir modelos de machine learning con una API ligera
- Proyectos serverless: AWS Lambda, Google Cloud Functions (footprint pequeño)
# Ejemplo perfecto para Flask: API para servir un modelo de ML
from flask import Flask, request, jsonify
import joblib
app = Flask(__name__)
modelo = joblib.load('modelo_prediccion.pkl')
@app.route('/predecir', methods=['POST'])
def predecir():
datos = request.get_json()
features = [datos['edad'], datos['ingreso'], datos['antiguedad']]
prediccion = modelo.predict([features])[0]
return jsonify({
'prediccion': int(prediccion),
'probabilidad': float(modelo.predict_proba([features])[0][1])
})
¿Cuándo Elegir Django?
Django es la mejor opción cuando:
- Aplicaciones web completas: E-commerce, CMS, dashboards, plataformas educativas
- Necesitas admin: El panel de administración automático ahorra semanas de desarrollo
- Autenticación compleja: Usuarios, roles, permisos, OAuth — todo incluido
- APIs robustas: Django REST Framework es el mejor framework de APIs en Python
- Equipos grandes: Las convenciones fuerzan consistencia entre desarrolladores
- Seguridad es prioridad: Protecciones incluidas por defecto (CSRF, XSS, SQL injection)
- Plazo ajustado: Arrancas más rápido cuando necesitas muchas funcionalidades
# Ejemplo perfecto para Django: plataforma con usuarios, admin, y API
# models.py
from django.db import models
from django.contrib.auth.models import User
class Curso(models.Model):
titulo = models.CharField(max_length=200)
instructor = models.ForeignKey(User, on_delete=models.CASCADE)
precio = models.DecimalField(max_digits=8, decimal_places=2)
publicado = models.BooleanField(default=False)
class Inscripcion(models.Model):
estudiante = models.ForeignKey(User, on_delete=models.CASCADE)
curso = models.ForeignKey(Curso, on_delete=models.CASCADE)
fecha = models.DateTimeField(auto_now_add=True)
completado = models.BooleanField(default=False)
# Con esto ya tienes:
# ✓ Panel admin completo para gestionar cursos e inscripciones
# ✓ Sistema de usuarios con login/registro
# ✓ Migraciones automáticas
# ✓ API REST con DRF en minutos
Resumen de la Decisión
| Si tu proyecto es... | Elige | Por qué |
|---|---|---|
| Una API pequeña o microservicio | Flask | Mínimo overhead, máxima simplicidad |
| Un prototipo/MVP rápido | Flask | Un archivo, cero configuración |
| Un servicio de ML/IA | Flask | Ligero, fácil de deployear en Lambda/Cloud Run |
| Una app web con usuarios | Django | Auth, permisos y admin incluidos |
| Un e-commerce o plataforma | Django | Admin, ORM, seguridad, DRF — todo listo |
| Un proyecto con equipo grande | Django | Convenciones fuertes = consistencia |
| Algo con muchas integraciones | Django | Ecosistema más cohesivo y maduro |
| Tu primer proyecto web | Flask (para aprender) / Django (para producir) | Flask enseña fundamentos, Django te hace productivo |
Conclusión
No hay un ganador absoluto entre Flask y Django — hay una herramienta correcta para cada situación. Flask brilla en microservicios, APIs ligeras y proyectos donde quieres control total sobre cada componente. Django domina en aplicaciones web completas, plataformas con usuarios y proyectos donde la velocidad de desarrollo importa más que la personalización granular.
En esta comparativa cubrimos:
- La filosofía de cada framework: microframework vs full-stack
- Código real comparando Hello World, modelos, APIs y autenticación
- Seguridad: Django incluye protecciones por defecto, Flask requiere extensiones
- El panel de administración de Django como ventaja competitiva
- Rendimiento: diferencia mínima en la práctica, ambos escalan bien
- Ecosistemas de extensiones y cuándo usar cada uno
- Guía de decisión práctica según el tipo de proyecto
La mejor recomendación: aprende ambos. Empieza con Flask para entender cómo funciona un framework web desde sus bases, y luego aprende Django para ser productivo en proyectos reales. Ambos frameworks son pilares del ecosistema Python y conocerlos te hará un desarrollador más completo.