Flask es uno de los frameworks web más populares del ecosistema Python. Creado por Armin Ronacher en 2010, combina simplicidad con flexibilidad, siendo la opción ideal tanto para principiantes como para desarrolladores experimentados que necesitan control granular sobre sus aplicaciones.

En este tutorial completo, aprenderás a crear una API REST profesional con Flask, desde la configuración inicial hasta el deployment en producción. Cubriremos todo lo que necesitas para construir APIs robustas y escalables.

🚀 ¿Por Qué Elegir Flask para Tu API?

Antes de sumergirte en el código, es importante entender por qué Flask se ha convertido en la elección de millones de desarrolladores en todo el mundo. Flask ofrece una curva de aprendizaje suave, permitiéndote comenzar a crear aplicaciones funcionales en solo unas pocas horas de estudio.

La filosofía "micro" de Flask significa que agregas solo las funcionalidades que necesitas, a diferencia de frameworks como Django que ya vienen con todo integrado. Este enfoque modular es perfecto para APIs REST, donde tienes control total sobre cada aspecto de la aplicación.

Además, Flask tiene una comunidad extremadamente activa y una documentación exemplary. Recursos como el Flask Mega-Tutorial de Miguel Grinberg son lectura obligatoria para cualquier desarrollador que quiera dominar este framework.

📦 Instalación y Configuración del Entorno

Para comenzar a desarrollar con Flask, primero necesitas configurar un entorno virtual aislado. Esto asegura que las dependencias de tu proyecto no entren en conflicto con otras instalaciones de Python en tu sistema.

Si aún no conoces los entornos virtuales, te recomiendo leer nuestra guía sobre venv Python - Entorno Virtual para entender este concepto fundamental. La configuración de un entorno virtual es considerada una de las mejores prácticas en el desarrollo profesional en Python.

# Crear entorno virtual
python -m venv venv-flask

# Activar en Windows
venv-flask\Scripts\activate

# Activar en Linux/Mac
source venv-flask/bin/activate

# Instalar Flask y extensiones
pip install flask flask-sqlalchemy flask-jwt-extended flask-cors

Flask-SQLAlchemy facilita la integración con bases de datos, mientras que Flask-JWT-Extended implementa autenticación mediante JSON Web Tokens. Flask-CORS permite que tu API sea accedida por aplicaciones front-end de diferentes dominios.

Es importante verificar la versión instalada de Flask para garantizar compatibilidad. Ejecuta el comando python -c "import flask; print(flask.__version__)" para confirmar la instalación.

🏗️ Estructura del Proyecto API REST

Una buena estructura de proyecto es fundamental para mantener el código organizado y escalable. Para APIs Flask, la arquitectura modular con blueprints es Highly Recommended por la comunidad.

Vamos a crear la siguiente estructura de directorios que separará responsabilidades y facilitará el mantenimiento del código en el futuro:

mi-proyecto-flask/
├── app/
│   ├── __init__.py
│   ├── models.py
│   ├── routes/
│   │   ├── __init__.py
│   │   ├── auth.py
│   │   └── products.py
│   └── utils/
├── run.py
└── requirements.txt

Esta estructura permite que cada módulo sea desarrollado y probado de forma independiente. Los modelos definen la estructura de la base de datos, mientras que las rutas agrupan los endpoints de la API por funcionalidad.

💻 Creando Tu Primera Ruta

Comencemos con el ejemplo más simple posible: una ruta que retorna un mensaje de bienvenida. Crea el archivo app/__init__.py con el siguiente código:

from flask import Flask, jsonify

def create_app():
    app = Flask(__name__)

    @app.route('/')
    def home():
        return jsonify({
            'mensaje': '¡Bienvenido a la API Flask!',
            'status': 'en linea',
            'version': '1.0.0'
        })

    @app.route('/salud')
    def health_check():
        return jsonify({'status': 'saludable'})

    return app

También crea el archivo run.py en la raíz del proyecto:

from app import create_app

app = create_app()

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Ejecuta el proyecto con python run.py y accede a http://localhost:5000 en tu navegador. Deberías ver la respuesta JSON con el mensaje de bienvenida. El parámetro debug=True activa el modo de debug, que reinicia automáticamente el servidor cuando realizas cambios en el código.

🗄️ Configurando Base de Datos con SQLAlchemy

SQLAlchemy es el ORM (Object-Relational Mapper) más popular de Python. Te permite interactuar con bases de datos usando objetos Python, abstractando la complejidad de las consultas SQL.

Configuremos una base de datos SQLite para nuestra API. SQLite es perfecta para desarrollo y pruebas, ya que no requiere instalar ningún servidor adicional:

# app/__init__.py actualizado
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_jwt_extended import JWTManager
from flask_cors import CORS

db = SQLAlchemy()
jwt = JWTManager()

def create_app():
    app = Flask(__name__)

    # Configuraciones
    app.config['SECRET_KEY'] = 'tu-clave-secreta-aqui'
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

    # Inicializar extensiones
    db.init_app(app)
    jwt.init_app(app)
    CORS(app)

    # Registrar blueprints
    from app.routes import auth_bp, products_bp
    app.register_blueprint(auth_bp, url_prefix='/api/auth')
    app.register_blueprint(products_bp, url_prefix='/api/productos')

    # Crear tablas
    with app.app_context():
        db.create_all()

    return app

La configuración de la base de datos es uno de los aspectos más importantes del desarrollo de APIs. SQLite es ideal para comenzar, pero para aplicaciones en producción puedes migrar fácilmente a PostgreSQL o MySQL cambiando solo la URI de conexión.

Para deployments en producción, considera usar servicios administrados como ElephantSQL o Railway que ofrecen planes gratuitos generosos y son fáciles de configurar.

📊 Creando Modelos de Datos

Los modelos definen la estructura de tus tablas en la base de datos. Crearemos modelos para usuarios y productos en nuestra API:

# app/models.py
from app import db
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash

class User(db.Model):
    __tablename__ = 'usuarios'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(256), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

    def to_dict(self):
        return {
            'id': self.id,
            'username': self.username,
            'email': self.email,
            'created_at': self.created_at.isoformat()
        }

class Product(db.Model):
    __tablename__ = 'productos'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    description = db.Column(db.Text)
    price = db.Column(db.Float, nullable=False)
    stock = db.Column(db.Integer, default=0)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    def to_dict(self):
        return {
            'id': self.id,
            'nombre': self.name,
            'descripcion': self.description,
            'precio': self.price,
            'stock': self.stock,
            'created_at': self.created_at.isoformat()
        }

Aquí estamos usando las funciones de seguridad de Werkzeug para hashear contraseñas. Esto es fundamental para la seguridad de tu aplicación - ¡nunca almacenes contraseñas en texto plano! El método to_dict() facilita la conversión de objetos a JSON.

🔐 Implementando Autenticación JWT

JSON Web Tokens (JWT) son el estándar moderno para autenticación en APIs REST. Permiten que el servidor verifique la identidad del cliente sin mantener sesiones activas.

Implementemos el sistema de autenticación con registro y login de usuarios:

# app/routes/auth.py
from flask import Blueprint, request, jsonify
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
from app import db
from app.models import User

auth_bp = Blueprint('auth', __name__)

@auth_bp.route('/registro', methods=['POST'])
def register():
    data = request.get_json()

    if not data or not data.get('username') or not data.get('password'):
        return jsonify({'error': 'Datos incompletos'}), 400

    if User.query.filter_by(username=data['username']).first():
        return jsonify({'error': 'Usuario ya existe'}), 409

    if User.query.filter_by(email=data['email']).first():
        return jsonify({'error': 'Email ya está en uso'}), 409

    new_user = User(
        username=data['username'],
        email=data['email']
    )
    new_user.set_password(data['password'])

    db.session.add(new_user)
    db.session.commit()

    return jsonify({'mensaje': 'Usuario creado exitosamente'}), 201

@auth_bp.route('/login', methods=['POST'])
def login():
    data = request.get_json()

    user = User.query.filter_by(username=data.get('username')).first()

    if not user or not user.check_password(data.get('password')):
        return jsonify({'error': 'Credenciales inválidas'}), 401

    access_token = create_access_token(identity=user.id)

    return jsonify({
        'access_token': access_token,
        'user': user.to_dict()
    }), 200

@auth_bp.route('/perfil', methods=['GET'])
@jwt_required()
def profile():
    current_user_id = get_jwt_identity()
    user = User.query.get(current_user_id)

    return jsonify(user.to_dict()), 200

La protección de rutas con @jwt_required() garantiza que solo usuarios autenticados puedan acceder a endpoints específicos. JWT es ampliamente soportado y recomendado por expertos en seguridad como la mejor opción para APIs modernas.

Para más información sobre autenticación segura, consulta la OWASP Authentication Cheat Sheet, que presenta las mejores prácticas de seguridad para sistemas de autenticación.

📝 Creando Rutas de Productos (CRUD Completo)

El patrón CRUD (Create, Read, Update, Delete) es la columna vertebral de cualquier API. Implementemos todas las operaciones para gestión de productos:

# app/routes/products.py
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from app import db
from app.models import Product

products_bp = Blueprint('products', __name__)

@products_bp.route('/', methods=['GET'])
def list_products():
    products = Product.query.all()
    return jsonify([p.to_dict() for p in products]), 200

@products_bp.route('/<int:product_id>', methods=['GET'])
def get_product(product_id):
    product = Product.query.get_or_404(product_id)
    return jsonify(product.to_dict()), 200

@products_bp.route('/', methods=['POST'])
@jwt_required()
def create_product():
    data = request.get_json()

    if not data.get('name') or not data.get('price'):
        return jsonify({'error': 'Nombre y precio son obligatorios'}), 400

    product = Product(
        name=data['name'],
        description=data.get('description', ''),
        price=data['price'],
        stock=data.get('stock', 0)
    )

    db.session.add(product)
    db.session.commit()

    return jsonify(product.to_dict()), 201

@products_bp.route('/<int:product_id>', methods=['PUT'])
@jwt_required()
def update_product(product_id):
    product = Product.query.get_or_404(product_id)
    data = request.get_json()

    product.name = data.get('name', product.name)
    product.description = data.get('description', product.description)
    product.price = data.get('price', product.price)
    product.stock = data.get('stock', product.stock)

    db.session.commit()

    return jsonify(product.to_dict()), 200

@products_bp.route('/<int:product_id>', methods=['DELETE'])
@jwt_required()
def delete_product(product_id):
    product = Product.query.get_or_404(product_id)

    db.session.delete(product)
    db.session.commit()

    return jsonify({'mensaje': 'Producto eliminado exitosamente'}), 200

Este código implementa todas las operaciones CRUD con protección de autenticación. Para operaciones de creación, actualización y eliminación, el usuario necesita estar logueado (token JWT válido).

La validación de datos es crucial para la seguridad de la API. Siempre valida los datos recibidos antes de procesarlos. Bibliotecas como Marshmallow son excelentes para crear esquemas de validación robustos.

🔍 Validación y Manejo de Errores

Una API profesional debe manejar errores de forma adecuada y retornar códigos HTTP correctos. Implementemos un manejador de errores global:

# Agregar en app/__init__.py
@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': 'Recurso no encontrado'}), 404

@app.errorhandler(500)
def internal_error(error):
    db.session.rollback()
    return jsonify({'error': 'Error interno del servidor'}), 500

@app.route('/api/productos/buscar')
def search_products():
    query = request.args.get('q', '')
    page = request.args.get('page', 1, type=int)
    per_page = request.args.get('per_page', 10, type=int)

    products = Product.query.filter(
        Product.name.ilike(f'%{query}%')
    ).paginate(page=page, per_page=per_page, error_out=False)

    return jsonify({
        'productos': [p.to_dict() for p in products.items],
        'total': products.total,
        'pagina': page,
        'total_paginas': products.pages
    }), 200

La paginación es esencial para APIs que retornan grandes cantidades de datos. Sin ella, puedes enfrentar problemas de rendimiento y timeout. SQLAlchemy ya provee soporte nativo para paginación con el método .paginate().

🚀 Deployment y Producción

Cuando tu API esté lista para producción, necesitarás un servidor robusto. Las opciones más populares para deploy de aplicaciones Flask incluyen:

  • Render: Plataforma gratuita con soporte para Python y deploy automático via GitHub
  • Railway: Excelente para aplicaciones que necesitan bases de datos integradas
  • Fly.io: Deploy global con edge computing para baja latencia
  • Heroku: Opción clásica con plan gratuito generoso

Para hacer deploy en Render, por ejemplo, necesitarás un archivo requirements.txt y un Procfile:

# requirements.txt
flask==3.0.0
flask-sqlalchemy==3.1.1
flask-jwt-extended==4.6.0
flask-cors==4.0.0
gunicorn==21.2.0
# Procfile
web: gunicorn run:app --workers 4

Gunicorn es un servidor WSGI production-ready que reemplaza el servidor de desarrollo de Flask. Gestiona múltiples workers para manejar varias solicitudes simultáneas.

Para configuraciones de producción, nunca uses el modo debug y siempre define variables de ambiente para contraseñas y claves secretas. ¡Nunca hagas commit de credenciales en GitHub!

📈 Buenas Prácticas y Próximos Pasos

Ahora que tienes una API REST funcional con Flask, aquí hay algunas mejoras que puedes implementar:

  • Rate Limiting: Limita el número de solicitudes por usuario para evitar abuso
  • Logging: Implementa logs estructurados para monitorear tu aplicación
  • Documentación: Usa Swagger/OpenAPI para documentar tus endpoints
  • Testing: Escribe pruebas unitarias y de integración
  • Migraciones de Base de Datos: Usa Flask-Migrate para gestionar cambios en la base de datos

Para hacer solicitudes a tu API, puedes usar la biblioteca Python Requests, que es el estándar para comunicación HTTP en Python. Permite probar todos los endpoints de tu API de forma programática.

Flask es increíblemente versátil y puede usarse para mucho más que APIs REST. Puedes crear aplicaciones web completas, dashboards de datos, chatbots e incluso APIs de machine learning. La comunidad Python ofrece miles de extensiones que expanden las capacidades del framework.

¡Sigue practicando y explorando la documentación oficial de Flask! Con dedicación, serás capaz de crear aplicaciones web robustas y profesionales en poco tiempo.