Si sabes algo de Python y quieres publicar algo en la web — un panel personal, una herramienta interna para tu trabajo, una API para una aplicación móvil o tu primera aplicación web de verdad — Flask es uno de los mejores lugares para empezar. Es lo bastante pequeño para aprenderlo en una tarde, pero lo bastante potente para ejecutar aplicaciones en producción en empresas de todos los tamaños.
Esta guía te lleva de una carpeta vacía a una aplicación desplegada. Aprenderás qué es Flask y cuándo usarlo, y luego construirás paso a paso una pequeña aplicación de lista de tareas: rutas, plantillas, formularios, una base de datos, una API JSON y pruebas. Por último, la desplegarás en un servidor real.
¿Qué Es Flask?
Flask es un framework web ligero para Python. A menudo se le llama microframework, lo que no significa que sea solo para proyectos pequeños — significa que Flask te da un núcleo pequeño y sólido, y te deja elegir todo lo demás.
De forma predeterminada, Flask se encarga de:
- Enrutamiento — asociar URLs como
/abouto/tasks/5con funciones de Python. - Peticiones y respuestas — leer datos de formularios, parámetros de consulta, JSON y cookies, y devolver HTML, JSON o redirecciones.
- Plantillas — generar HTML con el motor de plantillas Jinja2.
- Un servidor de desarrollo y un depurador — para ejecutar y probar tu aplicación localmente.
Lo que Flask no incluye — una capa de base de datos, cuentas de usuario, validación de formularios, un panel de administración — lo agregas con extensiones solo cuando lo necesitas. Algunas de las más populares son Flask-SQLAlchemy (bases de datos), Flask-Login (sesiones de usuario), Flask-WTF (formularios) y Flask-Migrate (migraciones de base de datos).
Flask vs. Django vs. FastAPI
Estos son los tres frameworks web de Python más populares, y cada uno tiene su punto fuerte:
- Flask — mínimo y flexible. Ideal para aplicaciones pequeñas y medianas, APIs, prototipos y para aprender cómo funcionan realmente las aplicaciones web.
- Django — “con baterías incluidas”. Trae un ORM, panel de administración, autenticación y mucho más. Ideal para sitios grandes con mucho uso de base de datos, donde prefieres que las convenciones ya estén decididas.
- FastAPI — diseñado para APIs modernas, con validación automática de datos y documentación interactiva de la API. Ideal cuando tu proyecto es principalmente una API JSON.
Si no estás seguro, Flask es una opción segura: lo que aprendas se transfiere fácilmente a los otros dos.
¿Por Qué Usar Flask? Los Beneficios
- Es fácil de aprender. Una aplicación Flask funcional cabe en cinco líneas de código. Hay muy poca “magia” — puedes leer tu aplicación de principio a fin y entender lo que pasa.
- Tú eliges tus herramientas. Usa cualquier base de datos, cualquier front-end, cualquier método de autenticación. Flask no te impone una estructura.
- Crece con tu proyecto. Empieza con un solo archivo y luego evoluciona a blueprints y una fábrica de aplicaciones a medida que tu aplicación crece.
- Es maduro y está bien documentado. Flask existe desde 2010, tiene una excelente documentación oficial y una gran comunidad con respuestas para casi cualquier pregunta.
- Es ideal para APIs. Devuelve un diccionario de Python desde una ruta y Flask lo convierte en JSON automáticamente.
- Es gratuito y de código abierto. Flask se publica bajo la licencia BSD y lo mantiene el proyecto Pallets.
Requisitos Previos
Para seguir esta guía necesitarás:
- Python 3.9 o más reciente (se recomienda una versión actual como 3.12 o 3.13). Si eres nuevo en el lenguaje, empieza con Fundamentos de Python: Lo que Todo Principiante Debe Saber.
- Una terminal y un editor de código como VS Code.
- Conocimientos básicos de HTML.
Paso 1: Configura tu Proyecto
Crea una carpeta para el proyecto y un entorno virtual para que Flask y sus dependencias queden aislados del resto de tu sistema:
mkdir flask-tasks
cd flask-tasks
python3 -m venv .venv
source .venv/bin/activate # En Windows: .venv\Scripts\activate
Luego instala Flask:
pip install flask
¿Prefieres uv? Si usas uv, el administrador de paquetes moderno de Python, ejecuta
uv inityuv add flask— uv crea y administra el entorno virtual por ti.
Confirma que se instaló correctamente:
flask --version
Paso 2: Tu Primera Aplicación Flask
Crea un archivo llamado hello.py:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Hello, Flask!"
Esto es lo que sucede:
Flask(__name__)crea tu aplicación.__name__le indica a Flask dónde buscar las plantillas y los archivos estáticos.@app.route("/")es un decorador que conecta la URL/con la funciónhome().- Lo que devuelva la función se envía de vuelta al navegador.
Ejecútala con el servidor de desarrollo:
flask --app hello run --debug
Abre http://127.0.0.1:5000 en tu navegador y verás “Hello, Flask!”. La opción --debug activa la recarga automática (el servidor se reinicia cuando guardas un archivo) y un depurador interactivo en el navegador cuando algo sale mal.
Nunca uses
--debugen producción. El depurador permite que cualquiera que vea la página de error ejecute código Python en tu servidor.
Paso 3: Rutas y URLs Dinámicas
Las aplicaciones reales necesitan más de una página. Agrega una ruta para cada URL y usa variables en la URL para las páginas dinámicas:
@app.route("/about")
def about():
return "This is the about page."
@app.route("/hello/<name>")
def hello(name):
return f"Hello, {name}!"
@app.route("/tasks/<int:task_id>")
def show_task(task_id):
return f"You asked for task number {task_id}."
Visitar /hello/Victor devuelve “Hello, Victor!”. El convertidor <int:task_id> asegura que el valor sea un número — /tasks/abc devuelve un error 404 en lugar de fallar. Otros convertidores son string (el predeterminado), float y path.
Métodos HTTP
De forma predeterminada, una ruta solo acepta peticiones GET. Para procesar formularios, permite también POST — o usa los decoradores abreviados @app.get() y @app.post():
from flask import request
@app.route("/contact", methods=["GET", "POST"])
def contact():
if request.method == "POST":
return "Thanks for your message!"
return "Show the contact form here."
Paso 4: Estructura tu Proyecto
Un solo archivo está bien para experimentar, pero se desordena rápido. En el resto de esta guía construiremos una pequeña aplicación de lista de tareas con una estructura que puede crecer:
flask-tasks/
├── app/
│ ├── __init__.py # fábrica de aplicaciones
│ ├── models.py # modelos de base de datos
│ ├── routes.py # rutas (un blueprint)
│ ├── static/
│ │ └── style.css
│ └── templates/
│ ├── base.html
│ └── index.html
├── tests/
│ └── test_app.py
├── .env
├── requirements.txt
└── wsgi.py # punto de entrada para servidores de producción
Dos patrones de Flask hacen que esta estructura funcione:
- La fábrica de aplicaciones — en lugar de crear
appal inicio de un archivo, la creas dentro de una funcióncreate_app(). Así es fácil crear aplicaciones con distintas configuraciones, por ejemplo, una para pruebas. - Blueprints — un blueprint es un grupo de rutas relacionadas. Las aplicaciones grandes dividen sus rutas en varios blueprints (
auth,blog,api, etc.) y los registran en la aplicación.
Paso 5: Agrega una Base de Datos con Flask-SQLAlchemy
Nuestra aplicación necesita guardar tareas. Instala Flask-SQLAlchemy, que conecta Flask con el ORM SQLAlchemy para que puedas trabajar con las filas de la base de datos como objetos de Python:
pip install flask-sqlalchemy python-dotenv
Crea app/__init__.py con la fábrica de aplicaciones:
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app(test_config=None):
app = Flask(__name__)
app.config.from_mapping(
SECRET_KEY=os.environ.get("SECRET_KEY", "dev"),
SQLALCHEMY_DATABASE_URI=os.environ.get("DATABASE_URL", "sqlite:///tasks.db"),
)
if test_config:
app.config.update(test_config)
db.init_app(app)
from .routes import bp
app.register_blueprint(bp)
with app.app_context():
db.create_all()
return app
Luego define un modelo en app/models.py. Cada clase se convierte en una tabla y cada atributo en una columna:
from . import db
class Task(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
done = db.Column(db.Boolean, default=False)
db.create_all() crea las tablas que todavía no existen. Con SQLite, el archivo de la base de datos se crea en una carpeta instance/ dentro de tu proyecto. SQLite es perfecto para aprender y para aplicaciones pequeñas; en producción puedes cambiar a PostgreSQL o MySQL modificando solo DATABASE_URL.
¿Vas a cambiar tus modelos más adelante?
db.create_all()no modifica las tablas existentes. Cuando tu aplicación ya esté en uso, agrega Flask-Migrate para administrar los cambios del esquema de forma segura.
Paso 6: Escribe las Rutas
Crea app/routes.py. Este blueprint muestra la lista de tareas, agrega tareas, las marca como completadas y las elimina:
from flask import Blueprint, flash, redirect, render_template, request, url_for
from . import db
from .models import Task
bp = Blueprint("tasks", __name__)
@bp.get("/")
def index():
tasks = db.session.execute(db.select(Task).order_by(Task.id)).scalars().all()
return render_template("index.html", tasks=tasks)
@bp.post("/add")
def add():
title = request.form.get("title", "").strip()
if not title:
flash("Please enter a task title.")
return redirect(url_for("tasks.index"))
db.session.add(Task(title=title))
db.session.commit()
flash("Task added.")
return redirect(url_for("tasks.index"))
@bp.post("/toggle/<int:task_id>")
def toggle(task_id):
task = db.get_or_404(Task, task_id)
task.done = not task.done
db.session.commit()
return redirect(url_for("tasks.index"))
@bp.post("/delete/<int:task_id>")
def delete(task_id):
task = db.get_or_404(Task, task_id)
db.session.delete(task)
db.session.commit()
flash("Task deleted.")
return redirect(url_for("tasks.index"))
Algunos detalles importantes:
request.formcontiene los datos enviados por un formulario HTML.redirect(url_for(...))devuelve al navegador a la lista después de un cambio. Este es el patrón Post/Redirect/Get: evita que el formulario se envíe dos veces si el usuario recarga la página.url_for("tasks.index")construye las URLs a partir de los nombres de las funciones (blueprint.función) en lugar de escribir las rutas a mano, así tus enlaces no se rompen si una URL cambia.db.get_or_404()devuelve la tarea, o una página 404 si no existe.flash()guarda un mensaje que se muestra una sola vez en la siguiente página. Usa la sesión, por eso la aplicación necesita unaSECRET_KEY.
Paso 7: Plantillas con Jinja2
Devolver texto sirve para probar, pero las páginas reales necesitan HTML. Flask usa plantillas Jinja2, que se guardan en la carpeta templates/.
Empieza con un diseño base en app/templates/base.html. Todas las páginas lo compartirán:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Flask Tasks{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<main>
{% for message in get_flashed_messages() %}
<p class="flash">{{ message }}</p>
{% endfor %}
{% block content %}{% endblock %}
</main>
</body>
</html>
Luego crea app/templates/index.html, que extiende el diseño base y completa el bloque content:
{% extends "base.html" %}
{% block content %}
<h1>My Tasks</h1>
<form action="{{ url_for('tasks.add') }}" method="post">
<input type="text" name="title" placeholder="What needs to be done?" required>
<button type="submit">Add</button>
</form>
<ul>
{% for task in tasks %}
<li class="{{ 'done' if task.done }}">
{{ task.title }}
<form action="{{ url_for('tasks.toggle', task_id=task.id) }}" method="post">
<button type="submit">{{ 'Undo' if task.done else 'Done' }}</button>
</form>
<form action="{{ url_for('tasks.delete', task_id=task.id) }}" method="post">
<button type="submit">Delete</button>
</form>
</li>
{% else %}
<li>No tasks yet. Add one above!</li>
{% endfor %}
</ul>
{% endblock %}
La sintaxis de Jinja2 es fácil de aprender:
{{ ... }}muestra un valor.{% ... %}ejecuta lógica, como ciclosfor, condicionesify bloques.{% extends %}y{% block %}permiten que las páginas compartan un diseño común.
Jinja2 escapa el HTML automáticamente, así que si alguien escribe <script> como título de una tarea, se muestra como texto en lugar de ejecutarse en el navegador. Eso te protege de toda una categoría de ataques de cross-site scripting (XSS).
Por último, agrega algunos estilos sencillos en app/static/style.css:
body { font-family: system-ui, sans-serif; max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
li { display: flex; gap: 0.5rem; align-items: center; margin: 0.5rem 0; }
li form { display: inline; }
.done { text-decoration: line-through; color: #888; }
.flash { background: #eef6ff; padding: 0.5rem 1rem; border-radius: 6px; }
Paso 8: Configuración y Secretos
Nunca escribas secretos como tu SECRET_KEY o la contraseña de tu base de datos directamente en el código. Guárdalos en un archivo .env y agrega .env a tu .gitignore para que nunca termine en tu repositorio:
SECRET_KEY=replace-this-with-a-long-random-string
DATABASE_URL=sqlite:///tasks.db
Genera una clave secreta segura con:
python -c "import secrets; print(secrets.token_hex(32))"
Cuando python-dotenv está instalado, el comando flask carga .env automáticamente. Nuestra función create_app() lee los valores con os.environ.get().
Ahora crea wsgi.py en la raíz del proyecto. Los servidores de producción lo usarán como punto de entrada:
from app import create_app
app = create_app()
Ejecuta la aplicación:
flask --app app run --debug
Flask encuentra la función create_app() por sí solo. Abre http://127.0.0.1:5000 y agrega algunas tareas — ya tienes una aplicación web funcional con base de datos.
Paso 9: Crea una API JSON
Flask hace que las APIs sean sencillas: si una ruta devuelve un diccionario o una lista, Flask lo convierte en JSON automáticamente. Agrega estas rutas a app/routes.py:
@bp.get("/api/tasks")
def api_list_tasks():
tasks = db.session.execute(db.select(Task).order_by(Task.id)).scalars()
return [{"id": t.id, "title": t.title, "done": t.done} for t in tasks]
@bp.post("/api/tasks")
def api_create_task():
data = request.get_json(silent=True) or {}
title = str(data.get("title", "")).strip()
if not title:
return {"error": "title is required"}, 400
task = Task(title=title)
db.session.add(task)
db.session.commit()
return {"id": task.id, "title": task.title, "done": task.done}, 201
Devolver una tupla como (data, 201) establece el código de estado HTTP. Prueba la API con curl:
curl http://127.0.0.1:5000/api/tasks
curl -X POST http://127.0.0.1:5000/api/tasks \
-H "Content-Type: application/json" \
-d '{"title": "Learn Flask"}'
Paso 10: Prueba tu Aplicación
Flask incluye un cliente de pruebas que envía peticiones simuladas a tu aplicación sin iniciar un servidor. Instala pytest:
pip install pytest
Crea tests/test_app.py:
import pytest
from app import create_app
@pytest.fixture
def client():
app = create_app({
"TESTING": True,
"SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:",
})
return app.test_client()
def test_home_page(client):
response = client.get("/")
assert response.status_code == 200
assert b"My Tasks" in response.data
def test_add_task(client):
response = client.post("/add", data={"title": "Write tests"}, follow_redirects=True)
assert b"Write tests" in response.data
def test_api_requires_title(client):
response = client.post("/api/tasks", json={})
assert response.status_code == 400
El fixture usa una base de datos SQLite en memoria, así que cada prueba empieza con una base de datos limpia y vacía. Precisamente por esto la fábrica de aplicaciones es tan útil. Ejecuta las pruebas desde la raíz del proyecto:
python -m pytest
Paso 11: Prepárate para Producción
El servidor de flask run está hecho solo para desarrollo — no está diseñado para ser rápido, estable ni seguro con tráfico real. Antes de desplegar, revisa esta lista:
- Desactiva el modo de depuración. Nunca ejecutes con
--debugoFLASK_DEBUG=1en producción. - Define una
SECRET_KEYreal desde una variable de entorno, nunca el valor predeterminado"dev". - Usa un servidor WSGI de producción como Gunicorn (Linux/macOS) o Waitress (funciona también en Windows).
- Fija tus dependencias para que el servidor instale exactamente lo que probaste:
pip install gunicorn
pip freeze > requirements.txt
- Usa una base de datos real como PostgreSQL si varias personas usarán la aplicación al mismo tiempo.
- Sirve tu sitio con HTTPS.
Prueba Gunicorn localmente antes de desplegar:
gunicorn -w 3 -b 127.0.0.1:8000 wsgi:app
wsgi:app significa “el objeto app dentro de wsgi.py”, y -w 3 inicia tres procesos de trabajo para que la aplicación pueda atender varias peticiones a la vez. Una regla común es usar (2 × núcleos de CPU) + 1 procesos.
Paso 12: Despliega tu Aplicación Flask
Hay tres formas comunes de desplegar una aplicación Flask. Elige la que mejor se adapte a tu experiencia y tu presupuesto.
Opción 1: Un Servidor Linux con Gunicorn y Nginx
Esta es la configuración clásica, y funciona en cualquier VPS (DigitalOcean, Linode, Hetzner, etc.) o en un servidor en casa. Gunicorn ejecuta tu código Python y Nginx se coloca delante para manejar HTTPS, los archivos estáticos y las conexiones entrantes.
1. Copia tu proyecto al servidor e instálalo:
sudo apt update && sudo apt install python3-venv nginx
cd /var/www
sudo git clone https://github.com/your-username/flask-tasks.git
sudo chown -R $USER:www-data flask-tasks
cd flask-tasks
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
Crea el archivo .env en el servidor con tu SECRET_KEY de producción.
2. Ejecuta Gunicorn como un servicio de systemd para que inicie con el sistema y se reinicie si falla. Crea /etc/systemd/system/flask-tasks.service:
[Unit]
Description=Flask Tasks (Gunicorn)
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/flask-tasks
EnvironmentFile=/var/www/flask-tasks/.env
ExecStart=/var/www/flask-tasks/.venv/bin/gunicorn -w 3 -b 127.0.0.1:8000 wsgi:app
Restart=always
[Install]
WantedBy=multi-user.target
El usuario www-data necesita permiso de escritura en la carpeta instance/, donde SQLite guarda la base de datos. Luego inicia el servicio:
sudo systemctl daemon-reload
sudo systemctl enable --now flask-tasks
sudo systemctl status flask-tasks
3. Configura Nginx como proxy inverso. Crea /etc/nginx/sites-available/flask-tasks:
server {
listen 80;
server_name tasks.example.com;
location /static/ {
alias /var/www/flask-tasks/app/static/;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Activa el sitio y recarga Nginx:
sudo ln -s /etc/nginx/sites-available/flask-tasks /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
4. Agrega HTTPS gratis con Let’s Encrypt:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d tasks.example.com
Certbot actualiza tu configuración de Nginx y renueva el certificado automáticamente.
Opción 2: Docker
Si ya usas Docker, empaquetar tu aplicación en un contenedor hace que funcione igual en todas partes. Agrega un Dockerfile en la raíz del proyecto:
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["gunicorn", "-w", "3", "-b", "0.0.0.0:8000", "wsgi:app"]
Agrega un archivo .dockerignore con .venv, .env, instance y __pycache__ para que no se copien a la imagen. Luego constrúyela y ejecútala:
docker build -t flask-tasks .
docker run -d -p 8000:8000 --env-file .env --name flask-tasks flask-tasks
Puedes poner la misma configuración de Nginx y Certbot de la Opción 1 delante del contenedor.
Opción 3: Una Plataforma como Servicio (PaaS)
Si prefieres no administrar un servidor, plataformas como Render, Railway, Fly.io y PythonAnywhere pueden desplegar una aplicación Flask directamente desde tu repositorio de GitHub. Normalmente solo tienes que:
- Subir tu código (con
requirements.txtywsgi.py) a GitHub. - Conectar el repositorio a la plataforma.
- Definir el comando de inicio como
gunicorn wsgi:app. - Agregar
SECRET_KEYyDATABASE_URLcomo variables de entorno en el panel de la plataforma.
Muchas de estas plataformas ofrecen planes gratuitos o económicos, perfectos para proyectos personales. Ten en cuenta que su sistema de archivos suele ser temporal, así que usa una base de datos administrada en lugar de SQLite.
Después de Desplegar: Monitorea tu Aplicación
Una vez que tu aplicación esté en línea, querrás saber de inmediato si deja de funcionar. Una herramienta autoalojada como Uptime Kuma puede revisar tu aplicación cada minuto y avisarte en tu teléfono en cuanto deje de responder.
Solución de Problemas Comunes en Flask
“Error: Could not locate a Flask application.” Flask no encuentra tu aplicación. Indícala explícitamente con --app, por ejemplo, flask --app hello run para un solo archivo, o flask --app app run para un paquete con create_app(). Asegúrate de ejecutar el comando desde la raíz del proyecto.
“Address already in use.” Otro programa está usando el puerto 5000. En macOS, el Receptor AirPlay suele usarlo. Ejecuta Flask en otro puerto con flask --app app run --port 5001.
Error TemplateNotFound. Flask busca las plantillas en una carpeta templates/ junto al módulo donde creaste la aplicación. Revisa el nombre de la carpeta (va en plural) y su ubicación.
“The session is unavailable because no secret key was set.” flash() y las sesiones necesitan una SECRET_KEY. Defínela en tu configuración o en el archivo .env.
“Working outside of application context.” Usaste db o current_app fuera de una petición — por ejemplo, en un script. Envuelve ese código en with app.app_context():.
Los cambios en los modelos no aparecen en la base de datos. db.create_all() solo crea las tablas que faltan; nunca modifica las existentes. Usa Flask-Migrate o, durante el desarrollo, borra el archivo de SQLite para empezar de cero.
Error 502 Bad Gateway de Nginx. Gunicorn no se está ejecutando o no escucha en la dirección indicada en proxy_pass. Revisa sudo systemctl status flask-tasks y sudo journalctl -u flask-tasks -n 50 para ver los errores.
Conclusión
Flask te da justo lo que necesitas para crear una aplicación web y no se interpone en todo lo demás. En esta guía pasaste de un “Hello, Flask!” de cinco líneas a una aplicación estructurada con base de datos, plantillas HTML, una API JSON y pruebas automatizadas — y luego la desplegaste con Gunicorn y Nginx, Docker o una plataforma de hosting.
A partir de aquí, la mejor forma de aprender es construir algo que realmente vayas a usar. Agrega cuentas de usuario con Flask-Login, administra los cambios de la base de datos con Flask-Migrate o convierte la API en el backend de un front-end en JavaScript. Como Flask se mantiene pequeño, cada nueva función que agregas te enseña un poco más sobre cómo funciona realmente la web.
