Flask: Crea aplicaciones web ligeras en Python
Flask es un microframework web para Python: ofrece lo estrictamente necesario para construir una aplicación web (enrutamiento, plantillas, gestión de peticiones) y te deja elegir el resto (base de datos, autenticación, formularios). Esta filosofía minimalista lo convierte en un excelente punto de partida para aprender desarrollo web en Python, y es lo bastante potente para aplicaciones en producción. Esta guía te acompaña desde la instalación hasta el despliegue.
Instalación y aplicación mínima
Crea un entorno virtual y luego instala Flask:
# Création et activation de l'environnement virtuel
python -m venv venv
# Windows
venv\Scripts\activate
# macOS / Linux
source venv/bin/activate
# Installation
pip install flask
Una aplicación Flask completa cabe en unas pocas líneas. Crea un archivo app.py:
from flask import Flask
# Création de l'application
app = Flask(__name__)
# Association d'une URL à une fonction (une "vue")
@app.route('/')
def accueil():
return "Bonjour, Flask !"
if __name__ == '__main__':
# debug=True : rechargement automatique et messages d'erreur détaillés
app.run(debug=True)
Lanza el servidor de desarrollo:
python app.py
# * Running on http://127.0.0.1:5000
Abre http://127.0.0.1:5000 en tu navegador: la página muestra «Bonjour, Flask !». No uses nunca debug=True en producción: el depurador integrado permite ejecutar código arbitrario.
Rutas y métodos HTTP
El decorador @app.route asocia una URL a una función. Por defecto, una ruta solo responde a peticiones GET; el parámetro methods permite otros verbos HTTP:
from flask import Flask, request
app = Flask(__name__)
@app.route('/contact', methods=['GET', 'POST'])
def contact():
if request.method == 'POST':
# Traitement de l'envoi du formulaire
return "Message reçu"
# Affichage du formulaire (GET)
return "Formulaire de contact"
| Método | Uso típico |
|---|---|
| GET | Leer un recurso, mostrar una página |
| POST | Crear un recurso, enviar un formulario |
| PUT | Reemplazar un recurso existente |
| PATCH | Modificar parcialmente un recurso |
| DELETE | Eliminar un recurso |
Parámetros de URL
Los segmentos variables de una URL se declaran entre corchetes angulares y se pasan como argumento a la función:
# Paramètre chaîne (par défaut)
@app.route('/utilisateur/<nom>')
def profil(nom):
return f"Profil de {nom}"
# Paramètre entier : /article/42 fonctionne, /article/abc renvoie 404
@app.route('/article/<int:article_id>')
def article(article_id):
return f"Article numéro {article_id}"
# Paramètre chemin : accepte les barres obliques
@app.route('/fichiers/<path:chemin>')
def fichier(chemin):
return f"Chemin demandé : {chemin}"
Conversores disponibles: string (por defecto), int, float, path, uuid.
Los parámetros de consulta (?tri=date&page=2) se leen con request.args:
@app.route('/recherche')
def recherche():
terme = request.args.get('q', '') # valeur par défaut : ''
page = request.args.get('page', 1, type=int) # conversion automatique
return f"Recherche de « {terme} », page {page}"
Plantillas Jinja2
Devolver HTML dentro de cadenas de Python se vuelve ilegible enseguida. Flask integra el motor de plantillas Jinja2: los archivos HTML viven en una carpeta templates/.
Estructura del proyecto:
mon_projet/
├── app.py
├── templates/
│ ├── base.html
│ └── index.html
└── static/
└── style.css
Herencia de plantillas
La plantilla base define la estructura común; las páginas rellenan los bloques:
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>{% block titre %}Mon Site{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<nav>
<a href="{{ url_for('accueil') }}">Accueil</a>
</nav>
<main>
{% block contenu %}{% endblock %}
</main>
</body>
</html>
<!-- templates/index.html -->
{% extends "base.html" %}
{% block titre %}Accueil - Mon Site{% endblock %}
{% block contenu %}
<h1>Bienvenue, {{ nom }} !</h1>
{% if articles %}
<ul>
{% for article in articles %}
<li>{{ article.titre }} ({{ article.date }})</li>
{% endfor %}
</ul>
{% else %}
<p>Aucun article pour le moment.</p>
{% endif %}
{% endblock %}
Del lado de Python, render_template carga la plantilla y le transmite variables:
from flask import render_template
@app.route('/')
def accueil():
articles = [
{"titre": "Premier article", "date": "2026-09-01"},
{"titre": "Deuxième article", "date": "2026-09-10"},
]
return render_template('index.html', nom="Alice", articles=articles)
Jinja2 escapa automáticamente el HTML de las variables, lo que protege frente a ataques XSS.
Archivos estáticos
Las hojas de estilo, los scripts y las imágenes van en la carpeta static/. La función url_for genera sus URL:
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo">
Usa siempre url_for en lugar de rutas escritas a mano: las URL siguen siendo correctas aunque la aplicación se despliegue bajo un prefijo.
Formularios y objeto request
Los datos de un formulario enviado por POST se leen con request.form:
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/inscription', methods=['GET', 'POST'])
def inscription():
if request.method == 'POST':
nom = request.form.get('nom', '').strip()
email = request.form.get('email', '').strip()
# Validation minimale
if not nom or not email:
return render_template('inscription.html',
erreur="Tous les champs sont obligatoires")
# Ici : enregistrement en base de données
return f"Bienvenue, {nom} !"
return render_template('inscription.html')
<!-- templates/inscription.html -->
{% extends "base.html" %}
{% block contenu %}
{% if erreur %}<p class="erreur">{{ erreur }}</p>{% endif %}
<form method="post">
<input type="text" name="nom" placeholder="Votre nom">
<input type="email" name="email" placeholder="Votre email">
<button type="submit">S'inscrire</button>
</form>
{% endblock %}
En proyectos reales, la biblioteca Flask-WTF añade validación y protección CSRF.
Redirecciones y mensajes flash
Tras un POST correcto, redirige al usuario (patrón «Post/Redirect/Get») y muestra un mensaje temporal con flash:
from flask import Flask, flash, redirect, url_for, render_template
app = Flask(__name__)
app.secret_key = "changez-moi-en-production" # requis pour flash et les sessions
@app.route('/enregistrer', methods=['POST'])
def enregistrer():
# ... traitement ...
flash("Enregistrement réussi !", "succes")
return redirect(url_for('accueil'))
<!-- Dans base.html, avant le bloc contenu -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% for categorie, message in messages %}
<div class="flash flash-{{ categorie }}">{{ message }}</div>
{% endfor %}
{% endwith %}
API JSON con jsonify
Flask es muy adecuado para las API REST. La función jsonify serializa un diccionario como respuesta JSON con la cabecera Content-Type correcta:
from flask import Flask, jsonify, request
app = Flask(__name__)
TACHES = [
{"id": 1, "titre": "Apprendre Flask", "faite": True},
{"id": 2, "titre": "Créer une API", "faite": False},
]
@app.route('/api/taches', methods=['GET'])
def lister_taches():
return jsonify(TACHES)
@app.route('/api/taches/<int:tache_id>', methods=['GET'])
def detail_tache(tache_id):
tache = next((t for t in TACHES if t["id"] == tache_id), None)
if tache is None:
return jsonify({"erreur": "Tâche introuvable"}), 404
return jsonify(tache)
@app.route('/api/taches', methods=['POST'])
def creer_tache():
donnees = request.get_json() # corps JSON de la requête
if not donnees or "titre" not in donnees:
return jsonify({"erreur": "Le champ 'titre' est requis"}), 400
nouvelle = {"id": len(TACHES) + 1, "titre": donnees["titre"], "faite": False}
TACHES.append(nouvelle)
return jsonify(nouvelle), 201
Prueba rápida con curl:
curl http://127.0.0.1:5000/api/taches
# [{"faite": true, "id": 1, "titre": "Apprendre Flask"}, ...]
curl -X POST http://127.0.0.1:5000/api/taches \
-H "Content-Type: application/json" \
-d '{"titre": "Déployer"}'
# {"faite": false, "id": 3, "titre": "Déployer"}
Estructurar con blueprints
Cuando la aplicación crece, un único archivo se vuelve inmanejable. Los blueprints agrupan las rutas por dominio funcional:
# blog/routes.py
from flask import Blueprint, render_template
blog_bp = Blueprint('blog', __name__, url_prefix='/blog')
@blog_bp.route('/')
def liste_articles():
return render_template('blog/liste.html')
@blog_bp.route('/<int:article_id>')
def detail(article_id):
return render_template('blog/detail.html', article_id=article_id)
# app.py
from flask import Flask
from blog.routes import blog_bp
app = Flask(__name__)
app.register_blueprint(blog_bp)
# Les routes /blog/ et /blog/<id> sont maintenant actives
Estructura típica de un proyecto organizado:
mon_projet/
├── app.py
├── blog/
│ └── routes.py
├── auth/
│ └── routes.py
├── templates/
└── static/
Configuración y variables de entorno
No pongas nunca secretos (claves, contraseñas de base de datos) en el código. Cárgalos desde el entorno:
import os
from flask import Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-uniquement')
app.config['DATABASE_URL'] = os.environ.get('DATABASE_URL')
app.config['DEBUG'] = os.environ.get('FLASK_DEBUG', '0') == '1'
En desarrollo, el paquete python-dotenv carga automáticamente un archivo .env (que debes excluir del repositorio Git mediante .gitignore):
# .env
SECRET_KEY=une-longue-chaine-aleatoire
DATABASE_URL=postgresql://user:pass@localhost/mabase
FLASK_DEBUG=1
Despliegue: gunicorn y nginx
El servidor integrado de Flask es monoproceso e inseguro: está reservado para el desarrollo. En producción, usa un servidor WSGI como Gunicorn detrás de un servidor web nginx:
pip install gunicorn
# 4 processus de travail, à l'écoute sur le port 8000
gunicorn --workers 4 --bind 127.0.0.1:8000 app:app
nginx actúa como proxy inverso: recibe el tráfico HTTPS, sirve directamente los archivos estáticos y transmite el resto a Gunicorn:
server {
listen 80;
server_name exemple.com;
location /static/ {
alias /var/www/mon_projet/static/;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Plataformas como Render, Railway o Fly.io automatizan este montaje si no quieres gestionar un servidor.
Flask vs Django
| Criterio | Flask | Django |
|---|---|---|
| Filosofía | Microframework, minimalista | Framework completo, «con baterías incluidas» |
| Curva de aprendizaje | Suave, se aprende según surgen las necesidades | Más pronunciada, muchos conceptos desde el principio |
| ORM | A elegir (SQLAlchemy es lo más habitual) | ORM de Django integrado |
| Administración | Se construye o mediante extensión | Interfaz de administración generada automáticamente |
| Autenticación | Extensión (Flask-Login) | Integrada |
| Estructura del proyecto | Libre | Impuesta y convencional |
| Ideal para | API, microservicios, sitios pequeños, prototipos | Aplicaciones completas con back-office |
Ambos frameworks son maduros y se utilizan en producción a gran escala. Elige Flask por su ligereza y flexibilidad, y Django cuando quieras el máximo de funcionalidades listas para usar.
Buenas prácticas
- Usa siempre un entorno virtual y un archivo
requirements.txt(pip freeze > requirements.txt). - Desactiva el modo debug en producción: es una vulnerabilidad de seguridad grave.
- Genera las URL con
url_foren lugar de escribirlas a mano. - Guarda los secretos en variables de entorno, nunca en el código fuente.
- Estructura con blueprints en cuanto la aplicación supere unas pocas rutas.
- Devuelve los códigos HTTP adecuados en tus API: 201 para una creación, 400 para una petición no válida, 404 para un recurso inexistente.
- Valida todas las entradas del usuario en el servidor, aunque el formulario ya las valide en el cliente.
Conclusión
Flask ilustra a la perfección la filosofía de Python: sencillo para empezar, extensible para crecer. Ahora sabes crear rutas, generar páginas con Jinja2, procesar formularios, exponer una API JSON, organizar tu código con blueprints y desplegar con Gunicorn y nginx. El siguiente paso lógico: conectar una base de datos con Flask-SQLAlchemy, gestionar la autenticación con Flask-Login y proteger tus formularios con Flask-WTF.
Para profundizar, consulta la documentación oficial de Flask, notablemente clara y completa.
Acerca de InSkillCoach
Experto en formación y tecnologías
Coach especializado en tecnologías avanzadas e IA, impulsado por GNeurone Inc.
Certificaciones:
- AWS Certified Solutions Architect – Professional
- Certificaciones de Google Cloud
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
Comentarios
Los comentarios funcionan con GitHub Discussions
Inicia sesión con GitHub para participar en la discusión