Litestar: APIs de Alto Rendimiento con Python
Construye APIs production-ready utilizando uno de los frameworks más modernos y performantes del ecosistema Python
Requisitos del proyecto
Para seguir este tutorial necesitas:
- Python 3.12+ (recomendamos 3.13 para mejor rendimiento)
- uv como gestor de dependencias (el más rápido del ecosistema Python)
- Conocimientos básicos de Python y APIs REST
Nota: Si quieres aprender más sobre uv y sus ventajas, tenemos un post específico sobre gestión de dependencias con Python.
Litestar es el framework ASGI que está revolucionando la forma de construir APIs en Python. Con un diseño async-first y una arquitectura limpia, ofrece rendimiento extremo sin sacrificar la elegancia del código. En este tutorial exploraremos cómo construir una API production-ready usando Litestar 2.20+.
Por qué Litestar en 2026
Litestar se ha consolidado como una de las mejores opciones para construir APIs de alto rendimiento:
- Rendimiento superior: Especialmente con
msgspeccomo serializador, puede manejar 10k+ req/s - "Batteries included": DI, middlewares, WebSockets, OpenAPI, caching, todo integrado
- Arquitectura limpia: Controllers clase-basados que escalan magnificamente
- Full async: Diseñado desde cero para async/await
- Type safety: Integración nativa con type hints y mypy
1. Inicialización del proyecto
1.1 Crear proyecto
# Crear el proyecto
uv init litestar-api
cd litestar-api
# Fijar versión de Python
uv python pin 3.13
Esto crea la estructura básica con pyproject.toml y uv.lock para dependencias reproducibles.
1.2 Añadir dependencias
# Core dependencies
uv add litestar uvicorn[standard]
# Serialización de alto rendimiento
uv add msgspec
# Base de datos (opcional)
uv add sqlalchemy asyncpg
# Desarrollo
uv add --dev pytest pytest-asyncio ruff mypy
Tu pyproject.toml:
[project]
name = "litestar-api"
version = "0.1.0"
description = "API de alto rendimiento con Litestar"
requires-python = ">=3.12"
dependencies = [
"litestar>=2.20.0",
"uvicorn[standard]>=0.34.0",
"msgspec>=0.19.0",
"sqlalchemy>=2.0.0",
"asyncpg>=0.30.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.25.0",
"ruff>=0.9.0",
"mypy>=1.14.0",
]
2. Aplicación mínima async-first
app/main.py:
from litestar import Litestar, get
@get("/")
async def health() -> dict[str, str]:
return {"status": "ok", "version": "1.0.0"}
@get("/health")
async def health_check() -> dict[str, str]:
return {"status": "healthy", "service": "litestar-api"}
app = Litestar(
route_handlers=[health, health_check],
debug=True,
)
Ejecutar:
# Con uvicorn
uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# O con el CLI de Litestar
uv run litestar run --reload
Endpoints generados automáticamente:
/schema- OpenAPI schema/docs- Documentación interactiva (Swagger UI, ReDoc, Stoplight)/openapi.json- Especificación OpenAPI
3. Arquitectura recomendada
Estructura de proyecto production-ready:
litestar-api/
│
├── app/
│ ├── __init__.py
│ ├── main.py # Entry point
│ ├── config.py # Configuración (pydantic-settings)
│ ├── dependencies.py # Inyección de dependencias
│ ├── controllers/ # Controladores por dominio
│ │ ├── __init__.py
│ │ └── users.py
│ ├── models/ # Modelos de datos
│ │ ├── __init__.py
│ │ └── user.py
│ ├── services/ # Lógica de negocio
│ │ ├── __init__.py
│ │ └── user_service.py
│ └── middleware/ # Middleware personalizado
│ └── __init__.py
│
├── tests/
│ ├── __init__.py
│ └── test_users.py
│
├── pyproject.toml
├── uv.lock
└── README.md
4. Controladores clase-basados
Los controllers permiten organizar endpoints por dominio de forma limpia:
from litestar import Controller, get, post, patch, delete
from litestar.dto import DTOConfig
from litestar.contrib.msgspec import MsgspecDTO
from msgspec import Struct
from uuid import UUID
# Modelo con msgspec (alto rendimiento)
class User(Struct):
id: UUID
name: str
email: str
age: int
class UserCreate(Struct):
name: str
email: str
age: int
class UserUpdate(Struct):
name: str | None = None
email: str | None = None
age: int | None = None
# DTO para respuestas parciales
class PartialUserDTO(MsgspecDTO[User]):
config = DTOConfig(exclude={"id"}, partial=True)
class UserController(Controller):
path = "/users"
tags = ["Users"]
# Inyección de servicio
dependencies = {"user_service": Provide(get_user_service)}
@get()
async def list_users(
self,
user_service: UserService,
limit: int = 100,
offset: int = 0
) -> list[User]:
return await user_service.list_users(limit, offset)
@post()
async def create_user(
self,
data: UserCreate,
user_service: UserService
) -> User:
return await user_service.create_user(data)
@get(path="/{user_id:uuid}")
async def get_user(
self,
user_id: UUID,
user_service: UserService
) -> User:
return await user_service.get_user(user_id)
@patch(path="/{user_id:uuid}", dto=PartialUserDTO)
async def update_user(
self,
user_id: UUID,
data: UserUpdate,
user_service: UserService
) -> User:
return await user_service.update_user(user_id, data)
@delete(path="/{user_id:uuid}")
async def delete_user(
self,
user_id: UUID,
user_service: UserService
) -> None:
await user_service.delete_user(user_id)
Registro en la aplicación:
from litestar import Litestar
from app.controllers.users import UserController
app = Litestar(
route_handlers=[UserController],
debug=True,
)
5. Serialización de alto rendimiento con msgspec
Litestar soporta múltiples backends de serialización:
| Backend | Rendimiento | Caso de uso |
|---|---|---|
msgspec | ⭐⭐⭐⭐⭐ | APIs de alto throughput |
Pydantic v2 | ⭐⭐⭐⭐ | Validación compleja |
dataclasses | ⭐⭐⭐ | APIs simples |
attrs | ⭐⭐⭐ | Casos específicos |
Ejemplo completo con msgspec
import msgspec
from litestar import post
from litestar.contrib.msgspec import MsgspecPlugin
# Structs son inmutables y typed
class Product(msgspec.Struct):
id: int
name: str
price: float
tags: list[str] = []
class Order(msgspec.Struct):
id: int
products: list[Product]
total: float
@post("/orders")
async def create_order(data: Order) -> Order:
# msgspec valida y deserializa automáticamente
# 2-5x más rápido que Pydantic v2
return data
# Configurar msgspec como serializador global
from litestar.serialization import DEFAULT_SERIALIZATION_BACKEND
from litestar.contrib.msgspec import MsgspecPlugin
app = Litestar(
route_handlers=[create_order],
plugins=[MsgspecPlugin()],
)
Beneficios técnicos de msgspec:
- Parsing binario optimizado en C
- Zero-copy deserialization donde sea posible
- Menor overhead de memoria que Pydantic
- Soporte nativo para JSON, MessagePack, YAML, TOML
6. Inyección de Dependencias (DI)
Sistema explícito y poderoso basado en Provide:
from litestar.di import Provide
from litestar import Litestar
from contextlib import asynccontextmanager
# Servicio de base de datos
class Database:
def __init__(self, dsn: str):
self.dsn = dsn
self._pool = None
async def connect(self):
# Inicializar pool de conexiones
pass
async def disconnect(self):
# Cerrar pool
pass
async def fetch(self, query: str) -> list[dict]:
return []
# Factory async para el servicio
async def get_database() -> Database:
db = Database("postgresql://localhost/db")
await db.connect()
return db
# Servicio de usuarios
class UserService:
def __init__(self, db: Database):
self.db = db
async def get_user(self, user_id: UUID) -> User:
data = await self.db.fetch(f"SELECT * FROM users WHERE id = {user_id}")
return User(**data[0])
async def get_user_service(db: Database) -> UserService:
return UserService(db)
# Configuración de la aplicación con DI
app = Litestar(
route_handlers=[UserController],
dependencies={
"db": Provide(get_database, use_cache=True), # Singleton por app
"user_service": Provide(get_user_service),
},
lifespan=[database_lifespan],
)
Uso en handlers:
@get("/users/{user_id:uuid}")
async def get_user(
user_id: UUID,
user_service: UserService # Inyectado automáticamente
) -> User:
return await user_service.get_user(user_id)
Características del DI de Litestar:
- Resolución por firma de tipo (type hints)
- Scopes:
app(singleton),request - Override limpio en tests
- Soporte para async factories
7. Plugin SQLAlchemy 2.0
uv add sqlalchemy asyncpg
Configuración moderna con SQLAlchemy 2.0:
from litestar.plugins.sqlalchemy import (
SQLAlchemyPlugin,
SQLAlchemyAsyncConfig,
)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import String
from uuid import uuid4
# Base declarativa
class Base(DeclarativeBase):
pass
# Modelo
class UserModel(Base):
__tablename__ = "users"
id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid4()))
name: Mapped[str] = mapped_column(String(100))
email: Mapped[str] = mapped_column(String(255), unique=True)
# Configuración del plugin
sqlalchemy_config = SQLAlchemyAsyncConfig(
connection_string="postgresql+asyncpg://user:pass@localhost/db",
create_all=True,
)
sqlalchemy_plugin = SQLAlchemyPlugin(config=sqlalchemy_config)
app = Litestar(
route_handlers=[UserController],
plugins=[sqlalchemy_plugin],
)
En los controllers:
from sqlalchemy.ext.asyncio import AsyncSession
from litestar.plugins.sqlalchemy import repository
class UserController(Controller):
path = "/users"
@post()
async def create_user(
self,
data: UserCreate,
db_session: AsyncSession, # Inyectado por el plugin
) -> User:
user = UserModel(name=data.name, email=data.email)
db_session.add(user)
await db_session.commit()
return User(id=user.id, name=user.name, email=user.email)
8. Middleware integrado y seguridad
CORS configuración
from litestar.middleware.cors import CORSMiddleware
from litestar.config.cors import CORSConfig
cors_config = CORSConfig(
allow_origins=["https://miapp.com", "https://admin.miapp.com"],
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
allow_credentials=True,
max_age=600,
)
app = Litestar(
route_handlers=[health, UserController],
middleware=[CORSMiddleware(config=cors_config)],
)
Rate limiting
from litestar.middleware.rate_limit import RateLimitConfig
rate_limit_config = RateLimitConfig(
rate_limit=("minute", 100),
exclude=["/health", "/schema"],
)
app = Litestar(
route_handlers=[...],
middleware=[rate_limit_config.middleware],
)
Autenticación JWT
from litestar.security.jwt import JWTAuth, JWTToken
from litestar.connection import ASGIConnection
from uuid import UUID
async def retrieve_user_handler(
token: JWTToken,
connection: ASGIConnection
) -> User | None:
return await user_service.get_by_id(UUID(token.sub))
jwt_auth = JWTAuth[
User,
Token,
](
retrieve_user_handler=retrieve_user_handler,
token_secret="super-secret-key",
exclude=["/login", "/health"],
)
app = Litestar(
route_handlers=[...],
on_app_init=[jwt_auth.on_app_init],
)
9. WebSockets en tiempo real
from litestar import websocket
from litestar.channels import ChannelsPlugin
from litestar.channels.backends.memory import MemoryChannelsBackend
channels_plugin = ChannelsPlugin(
backend=MemoryChannelsBackend(),
arbitrary_channels_allowed=True,
)
@websocket("/ws/notifications")
async def notification_handler(socket, channels: ChannelsPlugin) -> None:
await socket.accept()
async with channels.start_subscription("notifications") as subscriber:
async for message in subscriber:
await socket.send_json({"notification": message})
@post("/notify")
async def send_notification(
data: dict,
channels: ChannelsPlugin
) -> dict[str, str]:
await channels.publish(data["message"], channels=["notifications"])
return {"status": "sent"}
app = Litestar(
route_handlers=[notification_handler, send_notification],
plugins=[channels_plugin],
)
10. Lifecycle Hooks y gestión de recursos
from contextlib import asynccontextmanager
from litestar import Litestar
from collections.abc import AsyncGenerator
@asynccontextmanager
async def database_lifespan(app: Litestar) -> AsyncGenerator[None, None]:
# Startup
print("🚀 Iniciando aplicación...")
db = Database("postgresql://localhost/db")
await db.connect()
app.state.db = db
yield
# Shutdown
print("🛑 Cerrando aplicación...")
await db.disconnect()
# Hooks adicionales
async def startup() -> None:
print("✅ Servidor iniciado")
async def shutdown() -> None:
print("⏹️ Servidor detenido")
async def before_request(request) -> None:
pass
async def after_request(response) -> None:
return response
app = Litestar(
route_handlers=[...],
lifespan=[database_lifespan],
on_startup=[startup],
on_shutdown=[shutdown],
before_request=[before_request],
after_request=[after_request],
)
11. Testing integrado
import pytest
from litestar.testing import TestClient
from app.main import app
@pytest.fixture
def client() -> TestClient:
return TestClient(app)
@pytest.fixture
async def async_client() -> TestClient:
async with TestClient(app) as client:
yield client
async def test_create_user(async_client: TestClient) -> None:
response = await async_client.post("/users", json={
"name": "Juan Pérez",
"email": "juan@example.com",
"age": 30
})
assert response.status_code == 201
data = response.json()
assert data["name"] == "Juan Pérez"
assert "id" in data
async def test_get_user_not_found(async_client: TestClient) -> None:
response = await async_client.get("/users/123e4567-e89b-12d3-a456-426614174000")
assert response.status_code == 404
Ejecutar tests:
uv run pytest -v
12. Docker Production-Ready
Dockerfile:
# Build stage
FROM python:3.13-slim as builder
WORKDIR /app
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-install-project
# Production stage
FROM python:3.13-slim
WORKDIR /app
COPY --from=builder /bin/uv /bin/uv
COPY --from=builder /app/.venv /app/.venv
COPY app/ ./app/
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONPATH="/app"
RUN useradd -m -u 1000 appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
docker-compose.yml:
version: '3.8'
services:
api:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/litestar
- JWT_SECRET=${JWT_SECRET}
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: litestar
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Litestar vs FastAPI: Comparativa
✅ Ventajas de Litestar
| Aspecto | Litestar | FastAPI |
|---|---|---|
| Rendimiento | 10-20% más rápido con msgspec | Muy bueno con Pydantic v2 |
| Incluido | DI, middlewares, WebSockets, caching | Requiere más extensiones |
| Arquitectura | Controllers + DI nativo | Functions + Depends |
| Serialización | msgspec nativo | Pydantic principal |
| Documentación | Múltiples UIs (Swagger, ReDoc, Elements) | Swagger/ReDoc |
❌ Desventajas
- Ecosistema más pequeño: Menos tutoriales y Stack Overflow (no nos importa, para eso tenemos la IA?)
- Curva de aprendizaje: Más conceptos que entender (DI, scopes, plugins)
- Menor adopción: Menos ofertas de trabajo específicas
¿Cuándo usar Litestar?
✅ Ideal para:
- APIs de alto throughput: >10k req/s
- Microservicios: Arquitectura limpia y testeable
- Proyectos enterprise: Necesitas estructura y DI sólido
- Equipos experimentados: Valoran arquitectura sobre velocidad inicial
❌ No ideal para:
- MVP rápidos: FastAPI tiene más ejemplos y es más rápido de empezar
- Equipos junior: Menor comunidad para consultar
- Integraciones específicas: Algunas librerías tienen mejor soporte para FastAPI
Resumen: Comandos esenciales
# Desarrollo
uv run litestar run --reload
uv run pytest -v
uv run ruff check .
uv run mypy app/
# Producción
docker-compose up --build
uv run uvicorn app.main:app --workers 4
Recursos adicionales
Última actualización: Febrero 2026 | Litestar 2.20+