FastAPI lleva unos años siendo la respuesta habitual cuando alguien pregunta "¿qué uso para hacer una API en Python?". No es casualidad. Sebastián Ramírez lo creó con un objetivo claro: aprovechar los type hints de Python para que el código sea, a la vez, el servidor, la validación y la documentación. Y funciona.
En 2026, según las encuestas anuales de JetBrains sobre el estado de Python, FastAPI es el framework más elegido en proyectos nuevos de API. Por delante de Flask y Django REST Framework. Si todavía no lo conoces, o lo usas a medias, este artículo cubre lo que necesitas.
Qué hace diferente a FastAPI
La mayoría de frameworks te obligan a declarar rutas, validar entradas y documentar la API como tres tareas separadas. Con FastAPI escribes los type hints de Python y el framework hace las tres cosas solo.
Está construido sobre Starlette para el servidor HTTP y Pydantic v2 para la validación. Es ASGI, así que soporta async/await de serie. Y no hay decoradores mágicos ni ficheros de configuración XML: lo que escribes es lo que obtienes.
La estructura básica
Una aplicación mínima tiene este aspecto:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"mensaje": "hola"}
Para arrancar el servidor de desarrollo:
uvicorn main:app --reload
El flag --reload reinicia el proceso cada vez que guardas. En producción lo quitas y configuras workers con Gunicorn o directamente con uvicorn en modo multi-worker.
En cuanto a los tipos de retorno, puedes devolver un diccionario, un modelo Pydantic o una Response explícita si necesitas controlar las cabeceras o el código de estado manualmente.
Parámetros de ruta, query y body
FastAPI distingue entre tipos de parámetros según cómo los declares:
Parámetros de ruta
@app.get("/users/{id}")
def get_user(id: int):
...
El tipo int hace dos cosas: valida que lo que llegue en la URL sea un número y convierte el string automáticamente.
Parámetros query
@app.get("/users")
def list_users(page: int = 1, limit: int = 10):
...
Si tienen valor por defecto, son opcionales. Sin valor por defecto, son obligatorios. FastAPI genera la documentación correspondiente sin que tengas que escribir nada más.
Body
@app.post("/users")
def create_user(user: CreateUserRequest):
...
Si el parámetro es un modelo Pydantic, FastAPI lo lee del cuerpo de la petición.
Headers y cookies
from fastapi import Header
@app.get("/me")
def me(token: str = Header(...)):
...
Los tres puntos (...) indican que el parámetro es obligatorio. Lo mismo funciona con Cookie, Query y Path si quieres añadir validaciones extra como longitudes mínimas o rangos numéricos.
Modelos Pydantic: validación automática
Pydantic v2 es significativamente más rápido que v1 y tiene una API más limpia. Un modelo típico:
from pydantic import BaseModel, EmailStr, Field
class CreateUserRequest(BaseModel):
nombre: str
email: EmailStr
edad: int = Field(ge=18)
Si mandas un body con edad: 15, FastAPI devuelve un 422 con el detalle exacto del error. No tienes que escribir ni una línea de validación manual.
En el lado de la respuesta, response_model le dice a FastAPI qué campos devolver y cuáles filtrar:
@app.get("/users/{id}", response_model=UserResponse)
def get_user(id: int):
user = db.get(id)
return user # FastAPI serializa y filtra según UserResponse
Esto es útil cuando tu modelo interno tiene campos que no quieres exponer, como contraseñas hasheadas o campos internos de auditoría.
Dependencias: el sistema de inyección de FastAPI
El sistema de dependencias de FastAPI es una de sus partes más potentes y, al principio, la más rara de entender. La idea es simple: declaras una función que provee algo (una conexión a base de datos, el usuario actual, una configuración) y FastAPI la llama antes de ejecutar tu endpoint.
El caso más habitual es la conexión a base de datos:
from sqlalchemy.orm import Session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users")
def list_users(db: Session = Depends(get_db)):
return db.query(User).all()
El yield hace que FastAPI ejecute el código de limpieza (db.close()) una vez que el endpoint termina, aunque haya habido un error.
Las dependencias pueden anidarse: una dependencia puede tener sus propias dependencias, y FastAPI construye el árbol y lo resuelve en orden. Para autenticación, el patrón habitual es:
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def current_user(token: str = Depends(oauth2_scheme)):
# validar el token y devolver el usuario
...
@app.get("/profile")
def profile(user = Depends(current_user)):
return user
Rutas async y la integración con asyncio
FastAPI maneja bien tanto los endpoints síncronos como los asíncronos, pero hay que entender la diferencia para no cometer errores de rendimiento.
- async def: el endpoint se ejecuta en el event loop. Úsalo cuando hagas I/O asíncrono (consultas a base de datos con asyncpg, llamadas HTTP con httpx, etc.).
- def normal: FastAPI lo ejecuta en un thread pool para no bloquear el event loop. Úsalo con librerías síncronas como SQLAlchemy clásico o requests.
@app.get("/datos")
async def datos():
resultado = await fetch_from_db()
return resultado
El error típico es mezclar: llamar a una función síncrona bloqueante dentro de un async def. Eso bloquea el event loop entero. Si tienes que mezclar, usa asyncio.run_in_executor para mover el trabajo síncrono a un thread.
OpenAPI automático: la documentación que se escribe sola
Nada más arrancar la aplicación, FastAPI expone dos URLs:
/docs: Swagger UI interactivo. Puedes hacer peticiones reales desde el navegador./redoc: ReDoc, más limpio para leer la especificación.
Ambos se generan automáticamente a partir de tus type hints y modelos Pydantic. Si añades metadatos a las rutas, aparecen en la documentación:
@app.get(
"/users",
summary="Lista de usuarios",
tags=["users"],
response_model=list[UserResponse],
status_code=200
)
def list_users():
...
En proyectos con varios desarrolladores o con clientes externos que consumen la API, esto ahorra mucho tiempo. La especificación OpenAPI generada también sirve para generar clientes en otros lenguajes con herramientas como openapi-generator.
Testing con TestClient y httpx
FastAPI incluye un cliente de testing que no necesita levantar un servidor real:
from fastapi.testclient import TestClient
client = TestClient(app)
def test_get_user():
response = client.get("/users/1")
assert response.status_code == 200
assert response.json()["id"] == 1
Los tests son rápidos porque no hay puertos ni red de por medio: el cliente habla directamente con la app ASGI.
Para tests asíncronos, el combo habitual es pytest-asyncio con httpx.AsyncClient:
import pytest
import httpx
from httpx import ASGITransport
@pytest.mark.asyncio
async def test_async_endpoint():
async with httpx.AsyncClient(
transport=ASGITransport(app=app),
base_url="http://test"
) as client:
response = await client.get("/datos")
assert response.status_code == 200
Con este enfoque puedes testear endpoints asíncronos sin ningún servidor, igual de rápido que los tests síncronos.
Por dónde seguir
Si ya tienes la API levantada y quieres ir más lejos, estos artículos te pueden venir bien:
- FastAPI en producción: APIs multi-modelo con Python
- Python para herramientas de servidor: del CLI a la API
Imagen: Pexels / Kevin Ku
