Construye una API GraphQL segura con Python, FastAPI y Strawberry

Construye una API GraphQL segura con Python, FastAPI y Strawberry

Tutorial GraphQL Python con FastAPI, Strawberry y SQLAlchemy (async)

Un tutorial GraphQL Python paso a paso para construir una API moderna, segura y performante con FastAPI GraphQL, Strawberry GraphQL y SQLAlchemy async, lista para producción con JWT Python y Docker FastAPI.


1) Requisitos previos e instalación

1.1 Prerrequisitos

  • Python 3.11+ o 3.12+
  • pip o pipx
  • virtualenv o Poetry
  • Git
  • Docker y Docker Compose
  • PostgreSQL (cliente psql opcional)

1.2 Verificaciones rápidas

Explicación: confirma que tu entorno tiene las herramientas necesarias. Si falta alguna, instálala antes de continuar.

1.3 Crear y activar entorno virtual

Con venv:

Explicación: aislamos dependencias del proyecto para evitar conflictos.

Con Poetry (opcional):

Explicación: Poetry gestiona entornos y dependencias de forma reproducible.

Checklist de la sección:

  • [ ] Python y Docker instalados
  • [ ] Entorno virtual creado y activo
  • [ ] Git inicializado (git init)

2) Estructura del proyecto y configuración

2.1 Estructura recomendada

Explicación: organizamos por capas (API, esquema, datos, repositorios) para escalabilidad y mantenibilidad.

ASCII del flujo de una request GraphQL:

2.2 Configuración con Pydantic Settings v2

Archivo: app/config/settings.py

Explicación: Pydantic Settings lee variables desde .env según el entorno; evita hardcodear secretos.

Ejemplos de .env:

Explicación: define credenciales y opciones; en prod, monta secretos vía variables de entorno.

Checklist de la sección:

  • [ ] Estructura creada
  • [ ] .env preparado para dev/test/prod
  • [ ] Settings cargan sin errores

3) Dependencias e inicialización

3.1 Instalar dependencias

Paquetes clave: fastapi, strawberry-graphql[fastapi], uvicorn[standard], SQLAlchemy (async), asyncpg, alembic, pydantic, pydantic-settings, passlib[bcrypt], python-jose, aiodataloader, httpx, pytest, pytest-asyncio, sqlalchemy-utils, structlog, slowapi (opcional), y CORS (Starlette lo trae de serie).

pyproject.toml (ejemplo con versiones aproximadas)

Explicación: fija versiones aproximadas para reproducibilidad. Con pip, puedes generar un requirements.txt con pip freeze para pinnear exactas.

Instalación con Poetry:

Con pip (requirements.txt simple):

Explicación: instala dependencias de ejecución y de pruebas.

Checklist de la sección:

  • [ ] Dependencias instaladas
  • [ ] Proyecto ejecuta python -c "import fastapi, strawberry" sin error

4) Base de datos y migraciones

4.1 PostgreSQL en Docker

docker-compose.yml (DB + API en la misma red)

Explicación: levanta Postgres con healthcheck y un servicio API que usaremos luego.

4.2 SQLAlchemy async: motor y sesión

app/db/base.py

Explicación: Declarative Base estándar con convención de nombres estable.

app/db/session.py

Explicación: crea un AsyncEngine y un factory de sesiones. En prod, ajusta pool_size y timeouts.

4.3 Alembic y migraciones

alembic.ini (fragmento clave)

Explicación: usaremos env.py para leer DATABASE_URL desde settings.

migrations/env.py (async)

Explicación: env.py usa la URL de settings y permite migraciones async.

Crea la migración inicial y aplica:

Explicación: genera tablas a partir de modelos. Revisa el diff antes de aplicar.

Seeding simple (scripts/seed.py):

Explicación: inserta un usuario y algunos posts para pruebas.

Checklist de la sección:

  • [ ] docker-compose up de DB funciona
  • [ ] alembic upgrade head crea tablas
  • [ ] datos de ejemplo insertados

5) Modelado y repositorios (SQLAlchemy async)

5.1 Modelos: User, Post, Comment

app/models/user.py

Explicación: Users con UUID y relación 1-N hacia Post.

app/models/post.py

Explicación: Posts con FK a User y relación a Comments.

app/models/comment.py

Explicación: Comentarios vinculados a Post y User (para simplificar, omitimos relación inversa User.comments).

5.2 Repositorios async (CRUD + paginación)

app/repos/user_repo.py

Explicación: consultas básicas por id, email y creación.

app/repos/post_repo.py

Explicación: repositorio con paginación offset/limit y filtro básico por autor o búsqueda.

Checklist de la sección:

  • [ ] Modelos creados y migraciones reflejan cambios
  • [ ] Repos responden en REPL async

6) Esquema GraphQL con Strawberry

6.1 Tipos, inputs y scalars

app/schemas/graphql.py

Explicación: definimos tipos, queries y mutations. posts implementa paginación offset/limit. Mutations incluyen registro y login.

6.2 DataLoaders para evitar N+1

app/loaders/dataloaders.py

Explicación: agrupa consultas por lote, mitigando el problema N+1 en resolutores.

6.3 Ejemplos GraphQL (GraphiQL/cURL)

Query listar posts:

Explicación: pagina resultados y trae autor vía DataLoader.

Mutations:

Explicación: registro, login (retorna tokens) y creación de post autenticada.

cURL (con token):

Explicación: invoca la mutation autenticada enviando Authorization.

Checklist de la sección:

  • [ ] GraphiQL muestra el esquema
  • [ ] Queries y Mutations funcionan
  • [ ] DataLoader reduce queries (ver logs)

7) Integración con FastAPI

app/api/main.py

Explicación: monta /graphql con GraphiQL en dev, añade CORS, rate limiting opcional y endpoint /healthz.

Checklist de la sección:

  • [ ] /healthz responde ok
  • [ ] GraphiQL accesible en /graphql en dev

8) Autenticación y autorización (JWT Python)

app/auth/jwt.py

Explicación: genera y verifica tokens con expiración y tolerancia por reloj implícita al usar iat/exp. Las contraseñas se almacenan con bcrypt.

Autorización por rol/scope: puedes extender el payload JWT con claim “roles” y validarlo en los resolutores antes de ejecutar lógica.

Checklist de la sección:

  • [ ] Registro y login devuelven tokens
  • [ ] Mutations protegidas fallan sin token

9) Manejo de errores y validación

  • Validación: usa Inputs de Strawberry (tipos fuertes) y Pydantic para lógica interna si lo prefieres.
  • Errores: lanza GraphQLError con extensiones para códigos.

Ejemplo básico en mutation:

Explicación: GraphQL permite enriquecer el error para clientes.

Checklist de la sección:

  • [ ] Errores coherentes y predecibles
  • [ ] Inputs validan tipos

10) Paginación, filtros y ordenación

  • Offset/limit implementado en PostRepo y Query.posts.
  • Alternativa cursor-based (opcional): usa un cursor base64 con el id/created_at.

Ejemplo simple de cursor (idea):

Explicación: en producción, codifica múltiples campos para orden estable y soporta direcciones.

Checklist de la sección:

  • [ ] Paginación offset/limit probada
  • [ ] Filtros por autor y búsqueda activos

11) Observabilidad y salud

Logging estructurado con structlog (config mínima):

Explicación: logs en JSON facilitan correlación y análisis. Agrega un request-id en middleware si lo deseas.

Endpoint /healthz ya implementado. Para readiness, añade chequeo a DB.

Checklist de la sección:

  • [ ] Logs en JSON
  • [ ] Healthz responde 200

12) Pruebas con pytest y httpx

tests/testgraphqlapi.py

Explicación: prueba endpoints básicos. Para integración real, configura una DB efímera y limpia datos tras cada test.

Cobertura:

CI: en GitHub Actions, agrega un servicio de Postgres y ejecuta migraciones antes de tests.

Checklist de la sección:

  • [ ] Tests mínimos verdes
  • [ ] Cobertura generada

13) Contenerización y ejecución (Docker FastAPI)

Dockerfile (multi-stage)

Explicación: dos etapas reducen el tamaño final. En producción, puedes usar gunicorn con UvicornWorker.

Producción con Gunicorn:

Explicación: ajusta workers según CPU (2-4) y timeouts razonables.

.dockerignore

Explicación: evita copiar archivos innecesarios y secretos.

Ejecución:

Explicación: construye la imagen y levanta API y DB con healthchecks.

Checklist de la sección:

  • [ ] Imagen construida
  • [ ] API accesible en 8000

14) Seguridad adicional y rendimiento

  • CORS: restringe ALLOWED_ORIGINS en prod.
  • Tamaño de request: configura clientmaxbody_size en el reverse proxy (Nginx) y valida inputs.
  • Rate limiting: SlowAPI activo con umbrales prudentes.
  • Anti N+1: DataLoader en campos con relaciones.
  • Índices: añade índices para columnas filtradas (title, author_id).
  • Performance SQL: usa EXPLAIN ANALYZE para identificar cuellos de botella.
  • Cache lectura: considera Redis para queries frecuentes no mutables.

Checklist de la sección:

  • [ ] CORS restringido
  • [ ] Índices creados
  • [ ] DataLoader aplicado

15) Checklist global, próximos pasos y FAQ

Checklist global de verificación:

  • [ ] tutorial GraphQL Python implementado con FastAPI GraphQL y Strawberry GraphQL
  • [ ] SQLAlchemy async funcionando con PostgreSQL y Alembic
  • [ ] JWT Python para autenticación (access/refresh)
  • [ ] Docker FastAPI con healthchecks
  • [ ] Tests y cobertura básicos

Próximos pasos:

  • Suscripciones GraphQL (WebSockets) para eventos en tiempo real.
  • CDN/WAF delante de la API (Cloudflare) y un reverse proxy (Traefik/Nginx).
  • Despliegue: Fly.io, Render, AWS ECS/Fargate o Kubernetes.
  • Observabilidad avanzada: OpenTelemetry + Grafana/Loki/Tempo.

Resolución de problemas comunes

  • Error de conexión a DB: verifica DATABASE_URL y que el servicio db esté healthy (docker compose ps).
  • alembic revision no detecta modelos: asegúrate de importar modelos en migrations/env.py.
  • Bcrypt error en Docker slim: instala build-essential en etapa builder (incluido en Dockerfile).
  • 401 Unauthorized en mutations: confirma que envías Authorization: Bearer y que no expiró.
  • N+1 en author: revisa que uses DataLoader en el field author.