FastAPI es el framework más usado para crear APIs REST en Python cuando la velocidad de desarrollo y el rendimiento importan. Su integración con Pydantic valida los datos de entrada automáticamente; su soporte nativo de async/await permite manejar miles de conexiones concurrentes; y genera documentación Swagger interactiva sin configuración extra.
Instalación y primer endpoint
# pip install fastapi uvicorn[standard]
from fastapi import FastAPI
app = FastAPI(title="Mi API", version="1.0.0")
@app.get("/")
def inicio():
return {"mensaje": "Hola desde FastAPI"}
@app.get("/saludo/{nombre}")
def saludar(nombre: str, mayusculas: bool = False):
msg = f"Hola, {nombre}"
return {"mensaje": msg.upper() if mayusculas else msg}
# Ejecutar: uvicorn main:app --reload
# Swagger UI: http://127.0.0.1:8000/docs
# ReDoc: http://127.0.0.1:8000/redoc
Modelos Pydantic para validación automática
FastAPI usa Pydantic para validar el body de las peticiones. Si el cliente envía datos que no cumplen el modelo, FastAPI devuelve automáticamente un 422 con detalles del error sin que tengas que escribir ni una línea de validación.
from fastapi import FastAPI
from pydantic import BaseModel, Field, EmailStr
app = FastAPI()
class TareaCreate(BaseModel):
titulo: str = Field(..., min_length=1, max_length=100)
descripcion: str = Field("", max_length=500)
prioridad: int = Field(1, ge=1, le=5)
class TareaResponse(BaseModel):
id: int
titulo: str
descripcion: str
prioridad: int
completada: bool = False
# Base de datos en memoria (para el ejemplo)
tareas: dict[int, TareaResponse] = {}
contador = 0
@app.post("/tareas", response_model=TareaResponse, status_code=201)
def crear_tarea(tarea: TareaCreate):
global contador
contador += 1
nueva = TareaResponse(id=contador, **tarea.model_dump())
tareas[contador] = nueva
return nueva
Routing con APIRouter
# routers/tareas.py
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
router = APIRouter(prefix="/tareas", tags=["tareas"])
class Tarea(BaseModel):
id: int
titulo: str
completada: bool = False
db: dict[int, Tarea] = {}
@router.get("/", response_model=list[Tarea])
def listar_tareas(completadas: bool | None = None):
if completadas is None:
return list(db.values())
return [t for t in db.values() if t.completada == completadas]
@router.get("/{tarea_id}", response_model=Tarea)
def obtener_tarea(tarea_id: int):
if tarea_id not in db:
raise HTTPException(status_code=404, detail="Tarea no encontrada")
return db[tarea_id]
@router.delete("/{tarea_id}", status_code=204)
def eliminar_tarea(tarea_id: int):
if tarea_id not in db:
raise HTTPException(status_code=404, detail="Tarea no encontrada")
del db[tarea_id]
# main.py from fastapi import FastAPI from routers import tareas app = FastAPI() app.include_router(tareas.router)
Handlers async para I/O
import asyncio
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/datos-externos")
async def obtener_datos():
async with httpx.AsyncClient() as client:
respuesta = await client.get("https://jsonplaceholder.typicode.com/todos/1")
return respuesta.json()
@app.get("/multi-fetch")
async def multi_fetch():
async with httpx.AsyncClient() as client:
tareas = [
client.get(f"https://jsonplaceholder.typicode.com/todos/{i}")
for i in range(1, 4)
]
respuestas = await asyncio.gather(*tareas)
return [r.json() for r in respuestas]
Depends() para inyección de dependencias
from fastapi import FastAPI, Depends, HTTPException, Header
app = FastAPI()
def verificar_api_key(x_api_key: str = Header(...)):
if x_api_key != "mi-clave-secreta":
raise HTTPException(status_code=401, detail="API key inválida")
return x_api_key
def paginacion(pagina: int = 1, tamaño: int = 10):
if pagina < 1:
raise HTTPException(status_code=400, detail="Página debe ser >= 1")
return {"offset": (pagina - 1) * tamaño, "limit": tamaño}
@app.get("/protegido", dependencies=[Depends(verificar_api_key)])
def endpoint_protegido():
return {"mensaje": "Acceso autorizado"}
@app.get("/articulos")
def listar_articulos(pag: dict = Depends(paginacion)):
# Simula paginación
todos = [{"id": i, "titulo": f"Artículo {i}"} for i in range(1, 101)]
return todos[pag["offset"]:pag["offset"] + pag["limit"]]
CRUD completo de tareas
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class TareaIn(BaseModel):
titulo: str
completada: bool = False
class TareaOut(TareaIn):
id: int
db: dict[int, TareaOut] = {}
_siguiente_id = 1
@app.get("/tareas", response_model=list[TareaOut])
def listar():
return list(db.values())
@app.post("/tareas", response_model=TareaOut, status_code=201)
def crear(t: TareaIn):
global _siguiente_id
tarea = TareaOut(id=_siguiente_id, **t.model_dump())
db[_siguiente_id] = tarea
_siguiente_id += 1
return tarea
@app.put("/tareas/{tid}", response_model=TareaOut)
def actualizar(tid: int, t: TareaIn):
if tid not in db:
raise HTTPException(404, "No encontrada")
db[tid] = TareaOut(id=tid, **t.model_dump())
return db[tid]
@app.delete("/tareas/{tid}", status_code=204)
def eliminar(tid: int):
if tid not in db:
raise HTTPException(404, "No encontrada")
del db[tid]
Lo que hace a FastAPI distinto de Flask o Django REST: la validación sucede antes de que tu código se ejecute, la documentación se genera sola desde las anotaciones de tipo, y el soporte async es de primera clase. Para proyectos que necesitan una API REST sólida con mínimo boilerplate, FastAPI es la elección más productiva del ecosistema Python actual.
