FastAPI en Python: routing, modelos Pydantic, handlers async y documentación automática

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.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP