Python Framework

Flask

Flask es el micro-framework minimalista de Python. Obtienes enrutamiento, un objeto de petición y plantillas Jinja en un solo archivo, y luego añades exactamente las extensiones que necesites a medida que la aplicación crece.

beginner14 min readUpdated 16 sept 2026
app.py
python
# app.py
from flask import Flask, jsonify

app = Flask(__name__)


@app.get("/health")
def health():
    return jsonify(status="ok")


if __name__ == "__main__":
    app.run(debug=True)
Lanzamiento
2010
Lenguaje
Python
Estilo
Minimalista, no opinado
Idea central
Un objeto app
Plantillas
Jinja2
Versión actual
3.x

Por que importa

Por qué Flask es un gran primer framework

Un solo archivo para empezar

Importa Flask, crea una app, añade un decorador y ejecuta. No hay generador de proyectos ni una estructura de carpetas obligatoria que aprender primero.

Tú eliges las piezas

El núcleo es pequeño a propósito. La base de datos, la autenticación, los formularios y las migraciones provienen de extensiones bien mantenidas que tú decides integrar.

Python puro en todo momento

Las rutas son funciones, la configuración es un diccionario y la petición es un objeto que puedes leer. Nada se oculta detrás de una API de framework masiva.

La imagen completa

App, ruta, blueprint

Un objeto app, decoradores que mapean URLs a funciones y blueprints que agrupan rutas a medida que el proyecto crece.

El objeto app

Inicializar

Flask(__name__) crea la aplicación. Contiene la configuración, registra las rutas y es el objeto que el servidor WSGI invoca.

Rutas

Emparejar

El decorador @app.route vincula una regla de URL a una función. Los conversores capturan valores tipados como enteros y rutas.

Blueprints

Componer

Un Blueprint agrupa rutas, plantillas y archivos estáticos en un módulo que puede registrarse en la app, a menudo más de una vez.

HTML5 de un vistazo

La caja de herramientas de Flask

Enrutamiento por decoradores

@app.get("/posts/<int:id>") vincula un patrón de URL a una función de vista.

Petición y respuesta

Lee request.args, request.form y request.get_json(); devuelve strings, dicts u objetos Response.

Plantillas Jinja2

render_template rellena HTML con datos y escapa las variables por defecto.

Blueprints

Divide una app grande en módulos con prefijos y sus propias plantillas.

Extensiones

SQLAlchemy, Login, Migrate y otros añaden las piezas que el núcleo deja fuera.

Cliente de pruebas

app.test_client() realiza peticiones en el mismo proceso sin necesidad de ejecutar un servidor.

Una breve historia

De una broma del April Fools a un pilar de la web

  1. 2010

    Una broma se convierte en framework

    Armin Ronacher lanza Flask, construido sobre Werkzeug y Jinja2, después de que un proyecto de April Fools despertara un interés real.

    10
  2. 2011

    El ecosistema de extensiones

    Flask-SQLAlchemy y otras extensiones establecen el modelo de "núcleo pequeño, piezas opcionales".

    11
  3. 2018

    Flask 1.0

    Tras años de versiones 0.x, Flask se compromete con una API pública estable.

    18
  4. 2021

    Flask 2.0

    Llegan las vistas asíncronas y decoradores cortos como @app.get, modernizando la experiencia del desarrollador.

    21
  5. 2023

    Flask 3.0

    Un núcleo más ligero elimina APIs obsoletas hace tiempo y ajusta el contexto de la petición.

    23
  6. Hoy

    Sigue siendo el estándar minimalista

    Flask sigue siendo la opción preferida para servicios pequeños, prototipos y para enseñar los fundamentos de la web.

    Hoy

La guia completa

Flask: Todo lo que necesitas saber

¿Qué es Flask?

Flask es un framework web minimalista para Python. Te proporciona un objeto de aplicación, una forma de mapear URLs a funciones y un modelo de solicitud y respuesta. Todo lo demás —bases de datos, autenticación, formularios, tareas en segundo plano— es una decisión que tomas más adelante, generalmente añadiendo una extensión.

Esa simplicidad es precisamente su objetivo. Django tiene una opinión definida sobre cómo debe estructurarse un proyecto; Flask casi no tiene ninguna. Para una API pequeña, una herramienta interna o un primer proyecto web, esa libertad es una ventaja. Puedes leer el framework completo en una tarde y entender cada línea de tu propia aplicación.

Flask está construido sobre dos librerías consolidadas: Werkzeug para la infraestructura WSGI y Jinja2 para las plantillas. Ambas son maduras y están bien documentadas, y saber que están en la base explica la mayor parte del comportamiento de Flask.

La aplicación de Flask más pequeña

Una aplicación completa de Flask puede caber en unas pocas líneas.

# app.py
from flask import Flask

app = Flask(__name__)


@app.get("/")
def index():
    return "Hello, world"


if __name__ == "__main__":
    app.run(debug=True)

Flask(__name__) crea la aplicación y le indica dónde encontrar las plantillas y los archivos estáticos. El decorador vincula una URL a la función que tiene debajo. app.run() inicia el servidor de desarrollo. El guardián if __name__ == "__main__" evita que el servidor se inicie cuando el módulo es importado por una prueba o un servidor WSGI.

Enrutamiento con decoradores

El decorador @app.route es el corazón de Flask. Registra una regla de URL y la función que responde a ella.

@app.route("/posts")
def list_posts():
    return "All posts"


@app.route("/posts/<int:post_id>")
def get_post(post_id):
    return f"Post {post_id}"

Las reglas pueden contener partes variables escritas entre paréntesis angulares. Flask coincide con la URL, convierte el valor y lo pasa a la función como un argumento de palabra clave (keyword argument). Si ninguna regla coincide, Flask devuelve automáticamente un error 404.

Variables de URL y métodos HTTP

Los conversores hacen que las variables sean type-safe antes de que se ejecute tu código. Los más comunes son string (el predeterminado), int, float, path (que permite barras diagonales) y uuid.

@app.get("/users/<username>")
def profile(username):
    return f"Profile for {username}"


@app.get("/files/<path:filepath>")
def serve_file(filepath):
    return f"File at {filepath}"

Flask 2.0 añadió atajos para cada método. @app.get, @app.post, @app.put, @app.patch y @app.delete son más claros que pasar una lista methods, y una sola función puede responder a varios métodos.

@app.route("/posts", methods=["GET", "POST"])
def posts():
    if request.method == "POST":
        return create_post()
    return list_posts()

El objeto request

Los datos entrantes residen en el objeto global request, el cual Flask puebla para la solicitud actual. Es un proxy, por lo que importarlo una sola vez al principio del módulo es seguro.

from flask import request

@app.get("/search")
def search():
    query = request.args.get("q", "")
    page = request.args.get("page", 1, type=int)
    return {"query": query, "page": page}


@app.post("/posts")
def create_post():
    payload = request.get_json()
    title = payload["title"]
    return {"title": title}, 201

request.args contiene los valores de la query-string, request.form contiene los posts de formularios, request.get_json() parsea un cuerpo JSON, request.files contiene las subidas de archivos y request.headers proporciona los headers en bruto. Debido a que estos son mapeos de valores múltiples, utiliza .get() y proporciona valores por defecto en lugar de acceder a los índices a ciegas.

Respuestas y JSON

Una vista puede devolver una cadena, un diccionario (que Flask serializa a JSON), una tupla de (body, status) o (body, status, headers), o un objeto Response completo.

from flask import jsonify, make_response

@app.post("/posts")
def create_post():
    post = {"id": 1, "title": "Hello"}
    return jsonify(post), 201


@app.get("/ping")
def ping():
    response = make_response({"pong": True})
    response.headers["Cache-Control"] = "no-store"
    return response

jsonify es la forma explícita de devolver JSON: serializa los datos y establece el tipo de contenido. Devolver un diccionario también funciona, pero jsonify es más claro en cuanto a la intención y maneja más tipos de datos.

Plantillas con Jinja2

render_template carga un archivo de la carpeta templates/ y lo renderiza con los datos que le pases. Jinja2 escapa las variables por defecto, lo que protege contra XSS.

from flask import render_template

@app.get("/posts/<int:post_id>")
def get_post(post_id):
    post = {"id": post_id, "title": "Hello", "body": "First post"}
    return render_template("post.html", post=post)
<!-- templates/post.html -->
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>{{ post.title }}</title>
  </head>
  <body>
    <h1>{{ post.title }}</h1>
    <p>{{ post.body }}</p>
  </body>
</html>

Jinja soporta {% extends %} para la herencia, {% include %} para fragmentos, {% for %} y {% if %} para la lógica, y filtros como {{ value|upper }}. Mantén las plantillas enfocadas en la presentación; si la lógica crece, muévela a la vista.

Archivos estáticos

Por defecto, Flask sirve cualquier archivo en la carpeta static/ en /static/.... Haz referencia a ella mediante url_for, que construye la URL a partir del nombre del endpoint.

<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />

En producción, deja que un reverse proxy o un CDN sirvan los archivos estáticos en lugar del proceso de Python. Es más rápido y libera a la aplicación para gestionar las solicitudes.

Blueprints

Un Blueprint es una colección de rutas y plantillas que pueden registrarse en una aplicación. Es la forma en que una aplicación de Flask de un solo módulo crece hasta convertirse en un proyecto sin volverse un caos.

# blog/views.py
from flask import Blueprint, jsonify

bp = Blueprint("blog", __name__, url_prefix="/blog")


@bp.get("/")
def index():
    return jsonify(posts=[])


@bp.get("/<int:post_id>")
def detail(post_id):
    return jsonify(id=post_id)
# app.py
from blog.views import bp as blog_bp

app.register_blueprint(blog_bp)

Los Blueprints tienen su propio prefijo de URL, plantillas y archivos estáticos, y un mismo blueprint puede registrarse varias veces con diferentes prefijos. También facilitan las pruebas y la revisión de código porque cada funcionalidad es autónoma.

La factoría de la aplicación

Una vez que empiezas a usar blueprints, el siguiente paso natural es crear una función factoría que construya la aplicación. Esta es la estructura estándar para cualquier proyecto que sea más que una simple demo.

# app.py
from flask import Flask

from blog.views import bp as blog_bp


def create_app(config_object="config.Config"):
    app = Flask(__name__)
    app.config.from_object(config_object)

    app.register_blueprint(blog_bp)

    return app
# wsgi.py
from app import create_app

app = create_app()

La factoría hace que las pruebas sean triviales: construyes una aplicación con una configuración de test, ejecutas el cliente y luego la descartas. También evita la importación de una aplicación global medio configurada, que es el origen de la mayoría de los problemas de importaciones circulares en Flask.

Extensiones

Dado que el núcleo es minimalista, el ecosistema proporciona el resto. Unas pocas extensiones cubren la mayoría de los proyectos:

  • Flask-SQLAlchemy integra el ORM de SQLAlchemy y gestiona la sesión por solicitud.
  • Flask-Migrate envuelve Alembic para que los cambios de esquema estén versionados.
  • Flask-Login gestiona las sesiones de usuario y el proxy current_user.
  • Flask-WTF añade formularios, protección CSRF y subida de archivos.
  • Flask-CORS controla las solicitudes de origen cruzado para APIs.
# extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

db = SQLAlchemy()
migrate = Migrate()
# app.py
from extensions import db, migrate


def create_app():
    app = Flask(__name__)
    app.config.from_object("config.Config")

    db.init_app(app)
    migrate.init_app(app, db)

    return app

El patrón init_app permite que una extensión se cree sin una aplicación y se vincule posteriormente, lo que mantiene la factoría limpia y permite que la extensión sea importable por los modelos.

Configuración

La configuración es un mapeo simple en app.config. Cárgala desde una clase, un archivo o el entorno, y mantén los secretos fuera del control de versiones.

# config.py
import os


class Config:
    SECRET_KEY = os.environ["SECRET_KEY"]
    SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"]
    JSON_SORT_KEYS = False
app.config.from_object("config.Config")
app.config.from_prefixed_env()

from_prefixed_env() lee las variables de entorno de FLASK_*, lo cual es conveniente en contenedores. Utiliza diferentes clases o archivos por entorno, y nunca hagas commit del SECRET_KEY real; este se utiliza para firmar sesiones y tokens CSRF.

Manejo de errores

Registra manejadores de errores para devolver respuestas consistentes en toda la aplicación. Los manejadores pueden ser globales o estar limitados al alcance de un blueprint.

from flask import jsonify
from werkzeug.exceptions import HTTPException


@app.errorhandler(404)
def not_found(error):
    return jsonify(error="not_found"), 404


@app.errorhandler(Exception)
def unhandled(error):
    if isinstance(error, HTTPException):
        return error
    app.logger.exception(error)
    return jsonify(error="internal_error"), 500

El manejador para HTTPException es importante: los errores integrados de Flask también son excepciones, y de lo contrario, un manejador genérico de Exception convertiría un 404 en un 500.

Pruebas

El cliente de pruebas de Flask realiza solicitudes en el mismo proceso, sin necesidad de un servidor ni de red.

import pytest

from app import create_app


@pytest.fixture
def client():
    app = create_app("config.TestConfig")
    with app.test_client() as client:
        yield client


def test_health(client):
    response = client.get("/health")
    assert response.status_code == 200
    assert response.get_json() == {"status": "ok"}


def test_missing_post(client):
    response = client.get("/posts/999")
    assert response.status_code == 404

La factoría y test_client() trabajan en conjunto: cada prueba recibe una instancia limpia de la aplicación, evitando así que el estado se filtre entre pruebas. Sobrescribe la configuración para las pruebas y utiliza una base de datos en memoria cuando añadas una.

WSGI y despliegue

Flask es una aplicación WSGI, lo que significa que cualquier servidor WSGI puede ejecutarla. El servidor de desarrollo es exclusivamente para fines de desarrollo.

pip install gunicorn
gunicorn --workers 4 --bind 0.0.0.0:8000 "app:create_app()"

En producción, ejecuta Gunicorn o uWSGI detrás de Nginx, termina el TLS en el proxy y sirve los archivos estáticos desde el proxy o un CDN. Configura DEBUG=False, establece el logging y lee los secretos desde el entorno. Contenerizar la aplicación hace que el despliegue sea portable entre distintos hosts.

Mejores prácticas

  • Comienza con un único módulo, y luego migra a una factory y blueprints a medida que el proyecto crezca.
  • Utiliza url_for para construir URLs en lugar de escribir las rutas a mano.
  • Mantén la configuración en clases o variables de entorno, nunca directamente en el código.
  • Utiliza jsonify y códigos de estado explícitos para las respuestas de la API.
  • Valida cada dato de entrada antes de que llegue a tu lógica de negocio.
  • Registra manejadores de errores para que los fallos devuelvan una estructura consistente.
  • Utiliza el cliente de pruebas y una instancia limpia de la app por cada test.
  • Nunca ejecutes el servidor de desarrollo en producción.

Errores comunes

  • Dejar debug=True activado en producción, lo que expone un depurador interactivo.
  • Importar un app global en todas partes y crear importaciones circulares.
  • Leer request.json sin gestionar cuerpos mal formados o ausentes.
  • Escribir URLs fijas (hardcoding) en las plantillas en lugar de usar url_for.
  • Construir un app.py monolítico con cientos de rutas.
  • Olvidar la protección CSRF al utilizar formularios.
  • Almacenar secretos en config.py y hacer commit del archivo.
  • Asumir que Flask tiene un ORM o un sistema de autenticación integrado y terminar reinventando ambos de forma deficiente.

Próximos pasos

Flask es la forma más rápida de aprender qué hace realmente un framework web, ya que oculta muy pocos detalles. Cuando necesites un ORM, migraciones, autenticación y un panel de administración sin tener que ensamblarlos manualmente, lee la guía de Django. Si buscas validación automática y documentación de API basada en type hints, pásate a FastAPI. Para diseñar los endpoints que acabas de construir, la guía de REST APIs es el complemento ideal. Si decides añadir una base de datos, el backend roadmap cubre SQL, el lenguaje que se encuentra debajo del ORM.

Devolver JSON

jsonify serializa datos de Python, establece el tipo de contenido application/json y maneja los códigos de estado. json.dumps te obliga a construir toda la respuesta tú mismo.

Preferir
from flask import jsonify

@app.get("/posts/<int:id>")
def get_post(id):
    return jsonify(id=id, title="Hello")
Evitar
import json
from flask import Response

@app.get("/posts/<int:id>")
def get_post(id):
    body = json.dumps({"id": id, "title": "Hello"})
    return Response(body, mimetype="application/json")

Crear la app

Una función factoría permite que las pruebas creen una app limpia con configuración de test, mientras que una app a nivel de módulo se crea una sola vez al importar el módulo y es difícil de reconfigurar.

Preferir
def create_app(config=None):
    app = Flask(__name__)
    app.config.from_object(config or "config.Config")
    register_blueprints(app)
    return app
Evitar
app = Flask(__name__)
app.config["SECRET_KEY"] = "dev"

# imported everywhere; hard to test in isolation

Compromisos

¿Es Flask la opción predeterminada correcta?

Flask optimiza la libertad y una superficie de API pequeña. Esto es liberador para apps pequeñas y una carga una vez que la app crece.

Strengths

  • Entiendes todo el funcionamiento

    Hay muy poca magia. Cada ruta, valor de configuración y extensión es visible, lo que hace de Flask un framework excelente para aprender cómo funciona la web.

  • Minimalista y flexible

    Tú eliges la base de datos, la librería de validación y la estrategia de autenticación en lugar de heredar decisiones que no tomaste.

  • Perfecto para servicios pequeños

    Un microservicio JSON o una herramienta interna suelen ser un único archivo, y nada en el framework se opone a ello.

Trade-offs

  • Tú ensamblas el stack

    La autenticación, las migraciones, los formularios y el panel de administración son tu responsabilidad. Cada extensión tiene sus propios patrones y su propia curva de aprendizaje.

  • Tú inventas las convenciones

    Nada impone una estructura de proyecto, por lo que dos apps de Flask pueden verse completamente diferentes. Los equipos grandes necesitan su propio estilo de casa.

  • Menos funcionalidades integradas

    Las cabeceras de seguridad, el comportamiento del ORM y la validación son opcionales. Debes recordar añadirlos en lugar de estar protegido por defecto.

Preguntas frecuentes

Preguntas frecuentes

Keep learning

Related topics from the roadmap.

$ comienza a aprender

Listo para aprender Flask?

Nuestro tutorial interactivo te guia a traves de Flask paso a paso — con quizzes y codigo real que puedes ejecutar en el navegador.