¿Qué es una API REST?
Una API REST (Representational State Transfer) es un servicio web que permite la comunicación entre aplicaciones usando el protocolo HTTP. Flask es perfecto para crear APIs ligeras y eficientes.
Instalación y Setup
# Crear entorno virtual
python -m venv venv
# Activar entorno virtual
# Windows
venv\Scripts\activate
# Linux/Mac
source venv/bin/activate
# Instalar Flask
pip install flask
pip install flask-cors # Para CORS
# Opcional: para base de datos
pip install flask-sqlalchemyAPI Básica con Flask
# app.py
from flask import Flask, jsonify, request
app = Flask(__name__)
# Datos de ejemplo (en memoria)
usuarios = [
{'id': 1, 'nombre': 'Ana', 'email': 'ana@email.com'},
{'id': 2, 'nombre': 'Luis', 'email': 'luis@email.com'}
]
@app.route('/')
def home():
return jsonify({'mensaje': 'Bienvenido a mi API'})
if __name__ == '__main__':
app.run(debug=True)Métodos HTTP - CRUD Completo
GET - Obtener Datos
# Obtener todos los usuarios
@app.route('/api/usuarios', methods=['GET'])
def obtener_usuarios():
return jsonify({
'usuarios': usuarios,
'total': len(usuarios)
}), 200
# Obtener usuario específico
@app.route('/api/usuarios/<int:id>', methods=['GET'])
def obtener_usuario(id):
usuario = next((u for u in usuarios if u['id'] == id), None)
if usuario:
return jsonify(usuario), 200
else:
return jsonify({'error': 'Usuario no encontrado'}), 404POST - Crear Datos
# Crear nuevo usuario
@app.route('/api/usuarios', methods=['POST'])
def crear_usuario():
# Obtener datos del request
datos = request.get_json()
# Validar datos
if not datos or 'nombre' not in datos or 'email' not in datos:
return jsonify({'error': 'Datos incompletos'}), 400
# Crear nuevo usuario
nuevo_usuario = {
'id': len(usuarios) + 1,
'nombre': datos['nombre'],
'email': datos['email']
}
usuarios.append(nuevo_usuario)
return jsonify({
'mensaje': 'Usuario creado exitosamente',
'usuario': nuevo_usuario
}), 201PUT - Actualizar Datos
# Actualizar usuario completo
@app.route('/api/usuarios/<int:id>', methods=['PUT'])
def actualizar_usuario(id):
usuario = next((u for u in usuarios if u['id'] == id), None)
if not usuario:
return jsonify({'error': 'Usuario no encontrado'}), 404
datos = request.get_json()
# Validar datos
if not datos or 'nombre' not in datos or 'email' not in datos:
return jsonify({'error': 'Datos incompletos'}), 400
# Actualizar
usuario['nombre'] = datos['nombre']
usuario['email'] = datos['email']
return jsonify({
'mensaje': 'Usuario actualizado',
'usuario': usuario
}), 200PATCH - Actualización Parcial
# Actualizar campos específicos
@app.route('/api/usuarios/<int:id>', methods=['PATCH'])
def actualizar_parcial_usuario(id):
usuario = next((u for u in usuarios if u['id'] == id), None)
if not usuario:
return jsonify({'error': 'Usuario no encontrado'}), 404
datos = request.get_json()
# Actualizar solo campos proporcionados
if 'nombre' in datos:
usuario['nombre'] = datos['nombre']
if 'email' in datos:
usuario['email'] = datos['email']
return jsonify({
'mensaje': 'Usuario actualizado parcialmente',
'usuario': usuario
}), 200DELETE - Eliminar Datos
# Eliminar usuario
@app.route('/api/usuarios/<int:id>', methods=['DELETE'])
def eliminar_usuario(id):
global usuarios
usuario = next((u for u in usuarios if u['id'] == id), None)
if not usuario:
return jsonify({'error': 'Usuario no encontrado'}), 404
usuarios = [u for u in usuarios if u['id'] != id]
return jsonify({'mensaje': 'Usuario eliminado exitosamente'}), 200Manejo de Errores
# Error 404
@app.errorhandler(404)
def not_found(error):
return jsonify({'error': 'Recurso no encontrado'}), 404
# Error 500
@app.errorhandler(500)
def internal_error(error):
return jsonify({'error': 'Error interno del servidor'}), 500
# Error 400
@app.errorhandler(400)
def bad_request(error):
return jsonify({'error': 'Solicitud incorrecta'}), 400
# Manejo personalizado de excepciones
from werkzeug.exceptions import BadRequest
@app.errorhandler(BadRequest)
def handle_bad_request(e):
return jsonify({
'error': 'Datos inválidos',
'mensaje': str(e)
}), 400Validación de Datos
def validar_email(email):
import re
patron = r'^[\w\.-]+@[\w\.-]+\.\w+$'
return re.match(patron, email) is not None
@app.route('/api/usuarios', methods=['POST'])
def crear_usuario_validado():
datos = request.get_json()
# Validaciones
if not datos:
return jsonify({'error': 'No se enviaron datos'}), 400
if 'nombre' not in datos or not datos['nombre'].strip():
return jsonify({'error': 'El nombre es requerido'}), 400
if 'email' not in datos:
return jsonify({'error': 'El email es requerido'}), 400
if not validar_email(datos['email']):
return jsonify({'error': 'Email inválido'}), 400
# Verificar email duplicado
if any(u['email'] == datos['email'] for u in usuarios):
return jsonify({'error': 'El email ya existe'}), 409
# Crear usuario
nuevo_usuario = {
'id': len(usuarios) + 1,
'nombre': datos['nombre'],
'email': datos['email']
}
usuarios.append(nuevo_usuario)
return jsonify(nuevo_usuario), 201CORS - Cross-Origin Resource Sharing
from flask_cors import CORS
app = Flask(__name__)
# Permitir CORS para todas las rutas
CORS(app)
# O configuración específica
CORS(app, resources={
r"/api/*": {
"origins": ["http://localhost:3000"],
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_headers": ["Content-Type"]
}
})Paginación
@app.route('/api/usuarios', methods=['GET'])
def obtener_usuarios_paginados():
# Obtener parámetros de query
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
# Calcular índices
start = (page - 1) * per_page
end = start + per_page
# Paginar
usuarios_paginados = usuarios[start:end]
return jsonify({
'usuarios': usuarios_paginados,
'page': page,
'per_page': per_page,
'total': len(usuarios),
'pages': (len(usuarios) + per_page - 1) // per_page
}), 200Filtros y Búsqueda
@app.route('/api/usuarios/buscar', methods=['GET'])
def buscar_usuarios():
# Obtener parámetro de búsqueda
query = request.args.get('q', '').lower()
if not query:
return jsonify({'error': 'Parámetro de búsqueda requerido'}), 400
# Filtrar usuarios
resultados = [
u for u in usuarios
if query in u['nombre'].lower() or query in u['email'].lower()
]
return jsonify({
'resultados': resultados,
'total': len(resultados)
}), 200Integración con Base de Datos SQLite
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///api.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# Modelo
class Usuario(db.Model):
id = db.Column(db.Integer, primary_key=True)
nombre = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
fecha_creacion = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'nombre': self.nombre,
'email': self.email,
'fecha_creacion': self.fecha_creacion.isoformat()
}
# Crear tablas
with app.app_context():
db.create_all()
# CRUD con base de datos
@app.route('/api/usuarios', methods=['GET'])
def obtener_usuarios_db():
usuarios = Usuario.query.all()
return jsonify([u.to_dict() for u in usuarios]), 200
@app.route('/api/usuarios', methods=['POST'])
def crear_usuario_db():
datos = request.get_json()
try:
nuevo_usuario = Usuario(
nombre=datos['nombre'],
email=datos['email']
)
db.session.add(nuevo_usuario)
db.session.commit()
return jsonify(nuevo_usuario.to_dict()), 201
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 400
@app.route('/api/usuarios/<int:id>', methods=['GET'])
def obtener_usuario_db(id):
usuario = Usuario.query.get_or_404(id)
return jsonify(usuario.to_dict()), 200
@app.route('/api/usuarios/<int:id>', methods=['PUT'])
def actualizar_usuario_db(id):
usuario = Usuario.query.get_or_404(id)
datos = request.get_json()
usuario.nombre = datos.get('nombre', usuario.nombre)
usuario.email = datos.get('email', usuario.email)
try:
db.session.commit()
return jsonify(usuario.to_dict()), 200
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 400
@app.route('/api/usuarios/<int:id>', methods=['DELETE'])
def eliminar_usuario_db(id):
usuario = Usuario.query.get_or_404(id)
try:
db.session.delete(usuario)
db.session.commit()
return jsonify({'mensaje': 'Usuario eliminado'}), 200
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 400Autenticación Básica con JWT
pip install flask-jwt-extendedfrom flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
app.config['JWT_SECRET_KEY'] = 'tu-clave-secreta-super-segura'
jwt = JWTManager(app)
# Login
@app.route('/api/login', methods=['POST'])
def login():
datos = request.get_json()
email = datos.get('email')
password = datos.get('password')
# Validar credenciales (ejemplo simple)
if email == 'admin@email.com' and password == 'password123':
access_token = create_access_token(identity=email)
return jsonify({
'access_token': access_token,
'token_type': 'Bearer'
}), 200
return jsonify({'error': 'Credenciales inválidas'}), 401
# Ruta protegida
@app.route('/api/protegida', methods=['GET'])
@jwt_required()
def ruta_protegida():
usuario_actual = get_jwt_identity()
return jsonify({
'mensaje': 'Acceso permitido',
'usuario': usuario_actual
}), 200Testing de la API
# test_api.py
import unittest
import json
from app import app, db, Usuario
class APITestCase(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
self.app = app.test_client()
with app.app_context():
db.create_all()
def tearDown(self):
with app.app_context():
db.session.remove()
db.drop_all()
def test_obtener_usuarios(self):
response = self.app.get('/api/usuarios')
self.assertEqual(response.status_code, 200)
def test_crear_usuario(self):
datos = {
'nombre': 'Test Usuario',
'email': 'test@email.com'
}
response = self.app.post(
'/api/usuarios',
data=json.dumps(datos),
content_type='application/json'
)
self.assertEqual(response.status_code, 201)
self.assertIn('id', json.loads(response.data))
if __name__ == '__main__':
unittest.main()Documentación con Swagger
pip install flask-swagger-uifrom flask_swagger_ui import get_swaggerui_blueprint
SWAGGER_URL = '/api/docs'
API_URL = '/static/swagger.json'
swagger_blueprint = get_swaggerui_blueprint(
SWAGGER_URL,
API_URL,
config={'app_name': "Mi API"}
)
app.register_blueprint(swagger_blueprint, url_prefix=SWAGGER_URL)Ejemplo Completo: API de Tareas
# app.py - API completa
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tareas.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
CORS(app)
class Tarea(db.Model):
id = db.Column(db.Integer, primary_key=True)
titulo = db.Column(db.String(200), nullable=False)
descripcion = db.Column(db.Text)
completada = db.Column(db.Boolean, default=False)
fecha_creacion = db.Column(db.DateTime, default=datetime.utcnow)
fecha_vencimiento = db.Column(db.DateTime)
def to_dict(self):
return {
'id': self.id,
'titulo': self.titulo,
'descripcion': self.descripcion,
'completada': self.completada,
'fecha_creacion': self.fecha_creacion.isoformat(),
'fecha_vencimiento': self.fecha_vencimiento.isoformat() if self.fecha_vencimiento else None
}
with app.app_context():
db.create_all()
# Endpoints
@app.route('/api/tareas', methods=['GET'])
def obtener_tareas():
tareas = Tarea.query.all()
return jsonify([t.to_dict() for t in tareas]), 200
@app.route('/api/tareas', methods=['POST'])
def crear_tarea():
datos = request.get_json()
nueva_tarea = Tarea(
titulo=datos['titulo'],
descripcion=datos.get('descripcion', ''),
fecha_vencimiento=datetime.fromisoformat(datos['fecha_vencimiento']) if 'fecha_vencimiento' in datos else None
)
db.session.add(nueva_tarea)
db.session.commit()
return jsonify(nueva_tarea.to_dict()), 201
@app.route('/api/tareas/<int:id>/completar', methods=['PATCH'])
def completar_tarea(id):
tarea = Tarea.query.get_or_404(id)
tarea.completada = not tarea.completada
db.session.commit()
return jsonify(tarea.to_dict()), 200
if __name__ == '__main__':
app.run(debug=True)Probar la API con curl
# GET - Obtener todos
curl http://localhost:5000/api/usuarios
# GET - Obtener uno
curl http://localhost:5000/api/usuarios/1
# POST - Crear
curl -X POST http://localhost:5000/api/usuarios \
-H "Content-Type: application/json" \
-d '{"nombre":"Ana","email":"ana@email.com"}'
# PUT - Actualizar
curl -X PUT http://localhost:5000/api/usuarios/1 \
-H "Content-Type: application/json" \
-d '{"nombre":"Ana García","email":"ana@email.com"}'
# DELETE - Eliminar
curl -X DELETE http://localhost:5000/api/usuarios/1Buenas Prácticas
- Versionado: Usa /api/v1/ en tus rutas
- Códigos HTTP correctos: 200, 201, 400, 404, 500
- Validación: Siempre valida entrada del usuario
- Documentación: Documenta tus endpoints
- Seguridad: Usa HTTPS, valida tokens, sanitiza input
- Rate limiting: Limita requests por IP
- Logs: Registra todas las operaciones
Conclusión
Crear APIs REST con Flask es simple y poderoso. Esta base te permite construir servicios web escalables que pueden consumirse desde cualquier aplicación frontend, móvil o backend. Practica creando diferentes endpoints y agregando funcionalidades más complejas.