Saltar al contenidoJMRG
Todos los proyectos
COMPLETADO

Express Clean Backend

Template Node.js/TypeScript de producción: arquitectura hexagonal estricta, Keycloak OIDC, CQRS-lite, S3, Traefik 3.7 con ACME y stack Loki/Grafana/Prometheus.

Node.js · TypeScript · Hexagonal Architecture · Clean Architecture

Visión general

Backend Node 22 + TypeScript pensado como punto de partida para una API en producción. No es un boilerplate de "Hello World con autenticación": viene con IdP delegado a Keycloak, CQRS-lite a nivel de repositorios, auditoría desacoplada por eventos, storage S3 detrás de un port, y stack completo de logs y métricas tras Traefik con HTTPS automático.

La API está desplegada en api.jmrg.dev/api-docs y el endpoint de readiness en api.jmrg.dev/health/ready.

Motivación

Cada vez que arrancaba un proyecto Node desde cero acababa copiando los mismos cuatro módulos: el wrapper de Keycloak, el logger con correlación, el error handler con su chain, y el docker-compose con Traefik. Este template consolida todo eso en un repo con arquitectura hexagonal estricta y reglas verificables, así esas decisiones no se vuelven a tomar a mano en cada proyecto nuevo.

Decisiones que vale la pena explicar

Keycloak como IdP único. Cero JWT propietarios. La API valida RS256 contra el JWKS con jose. Los paneles humanos (Grafana, Traefik, Prometheus) se protegen con el plugin OIDC de Traefik apuntando al mismo realm, así no hay tres logins distintos para tres servicios.

Repositorios separados para lectura y escritura. UserQueryRepository y UserCommandRepository no comparten interfaz. Evita que un findById acabe arrastrando métodos de mutación y deja la puerta abierta a escalar a CQRS real cuando haga falta, sin reescribir el dominio.

Auditoría vía Observer. LoginUseCase publica eventos en un EventBusPort y AuditLoginObserver los persiste. El caso de uso no sabe que la auditoría existe.

Storage detrás de un port. StoragePort con S3StorageAdapter para AWS real (o LocalStack en dev) y FakeStorageAdapter para tests. El adapter incluye guard contra path-traversal porque alguien lo va a intentar.

Logging con contexto. Winston decorado con RequestContextLoggerDecorator que propaga el correlationId por AsyncLocalStorage. No hay que pasarlo por parámetro en cada función.

Observabilidad provisionada. Loki + Prometheus + Grafana con dashboards y datasources en JSON. Levanta y funciona, sin clickar dashboards a mano.

HTTPS automático. Traefik 3.7 emite el certificado vía Let's Encrypt en el primer arranque con HTTP-01 challenge y se encarga de renovarlo.

Arquitectura

Cuatro capas con dependencias dirigidas hacia el centro:

src/
├── domain/         # entidades, value objects, ports
├── application/    # use cases por feature
├── infrastructure/ # adapters: keycloak, mongo, s3, winston, events
└── presentation/   # routers Express + middlewares + bootstrap

La regla es simple: nada dentro de domain/ ni application/ puede importar mongoose, express, aws-sdk, keycloak-connect, jose ni winston. ESLint lo verifica en CI.

Stack

Capa Tecnología
Runtime Node ≥ 22, TypeScript strict, ESM nativo
HTTP Express 5 + helmet, cors, express-rate-limit
Auth Keycloak 26.6 (OIDC) + jose (JWKS RS256)
Persistencia MongoDB 8.2 + Mongoose
Storage AWS S3, LocalStack en dev
Validación Zod fail-fast por subsistema
Logs Winston → Loki 3.7
Métricas Prometheus 3.8 + Grafana 13
Reverse proxy Traefik 3.7 + plugin OIDC sevensolutions/traefik-oidc-auth
Testing Vitest + supertest + mongodb-memory-server
Package manager pnpm

Patrones de diseño

Categoría Patrón Dónde aparece
Estructural Hexagonal (Ports & Adapters) domain/<feature>/portinfrastructure/<feature>/adapter
Estructural Adapter KeycloakAdapter, S3StorageAdapter, WinstonLoggerAdapter
Estructural Facade UserFacade, AuthFacade, StorageFacade
Estructural Decorator RequestContextLoggerDecorator
Comportamiento Chain of Responsibility Error handler: Zod → Client → Server → Mongo → Fallback
Comportamiento Observer EventBusPort + AuditLoginObserver
Comportamiento Strategy Selector de formato en WinstonLoggerAdapter
Comportamiento Template Method ErrorHandler.handle / delegate
Creacional Factory Method CustomError.notFound, .conflict, .unauthorized
Creacional Singleton MongoDatabase, S3Client
DDD Repository CQRS-lite UserQueryRepository + UserCommandRepository

Endpoints

Método Endpoint Auth Roles
POST /api/v1/auth/register público, rate-limit 3/min/IP
POST /api/v1/auth/login público, rate-limit 5/min
POST /api/v1/auth/refresh público
GET /api/v1/auth/me Bearer cualquiera
GET /api/v1/users Bearer admin
POST /api/v1/users Bearer admin
PUT /api/v1/users/:id Bearer admin
DELETE /api/v1/users/:id Bearer admin
GET /api/v1/storage/objects Bearer admin
GET /api/v1/storage/signed-url Bearer admin
GET /health/ready público
GET /metrics LAN-only

Spec OpenAPI completo en api.jmrg.dev/api-docs.

Despliegue

En desarrollo, dos comandos:

docker compose --profile dev up -d                              # base + LocalStack
docker compose --profile dev --profile observability up -d      # + Loki/Grafana/Prom
pnpm dev                                                        # API en host con tsx watch

En producción se define .env.production:

DOMAIN=tudominio.com
SSL_EMAIL=[email protected]
KEYCLOAK_INTERNAL_URL=https://auth.tudominio.com

Y se arranca:

./scripts/init-acme.sh
docker compose --env-file .env.production up -d

Traefik emite el certificado de Let's Encrypt en el primer arranque y lo renueva automáticamente.

Documentación

14 capítulos en docs/ distribuidos en cuatro bloques:

  • Proyecto — estructura, arquitectura, features, seguridad, configuración, testing, contribución.
  • API — endpoints, schemas Zod, cadena de errores.
  • Infraestructura — Docker, Traefik, ACME, observabilidad.
  • Operación — runbook, healthchecks, troubleshooting.