Clean architecture backend built with Fastify, TypeScript, and Prisma. Synchronizes Meta Ads mock accounts, campaigns, and metrics with PostgreSQL, implementing hexagonal architecture and concurrency-safe sync flows.
- Clean Architecture: Hexagonal architecture with clear separation of concerns
- Fastify: High-performance web framework
- TypeScript: Full type safety
- Prisma: Type-safe database ORM
- PostgreSQL: Relational database
- Swagger/OpenAPI: Auto-generated API documentation
- Concurrency-Safe: Safe concurrent sync operations using Promise.allSettled
src/
βββ api/ # API layer (controllers, routes, schemas)
βββ application/ # Application layer (use cases, services)
βββ domain/ # Domain layer (entities, repositories interfaces, value objects)
βββ infrastructure/ # Infrastructure layer (Prisma, HTTP clients, config)
- Node.js v22 or higher
- PostgreSQL database (v14 or higher)
- npm or yarn
- Docker and Docker Compose (optional, for containerized deployment)
git clone <repository-url>
cd CampaignFlowAPInpm installCrea un archivo .env en la raΓz del proyecto con las siguientes variables:
# Database Configuration
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/meta_backend"
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=meta_backend
POSTGRES_PORT=5432
# Application Configuration
PORT=3000
NODE_ENV=development
# External API Configuration
META_API_BASE_URL=https://w5k577bkq5cmihbdxxqlok2c7y0ejbiz.lambda-url.us-east-1.on.aws
META_API_TIMEOUT=30000
META_API_RETRY_ATTEMPTS=3
# Sync Configuration
SYNC_ALL_TIMEOUT=300000Nota: Si usas Docker Compose, las credenciales por defecto son postgres:postgres.
OpciΓ³n A: Usando Docker (Recomendado)
# Iniciar solo PostgreSQL
docker-compose up -d postgres
# Verificar que estΓ‘ corriendo
docker-compose ps
# Esperar unos segundos para que PostgreSQL estΓ© listoOpciΓ³n B: PostgreSQL Local
AsegΓΊrate de tener PostgreSQL v14+ instalado y corriendo localmente, luego actualiza DATABASE_URL en .env con tus credenciales.
# Generar Prisma Client
npm run prisma:generate
# Ejecutar migraciones (crea las tablas)
npm run prisma:migrate
# Poblar la base de datos con datos iniciales (seed)
npm run prisma:seedNota: Cuando ejecutes prisma:migrate, se te pedirΓ‘ un nombre para la migraciΓ³n. Puedes usar init o initial_schema.
Si hay errores de conexiΓ³n, verifica:
- PostgreSQL estΓ‘ corriendo:
docker-compose ps(si usas Docker) DATABASE_URLen.envcoincide con tus credenciales- La base de datos
meta_backendexiste (Prisma la crea automΓ‘ticamente si no existe)
npm run devEl servidor estarΓ‘ disponible en http://localhost:3000
# Health check
curl http://localhost:3000/health
# DeberΓas recibir:
# {
# "status": "ok",
# "timestamp": "...",
# "database": { "status": "connected" }
# }Abre tu navegador en: http://localhost:3000/docs
Swagger UI te permitirΓ‘ probar todos los endpoints interactivamente.
β ConfirmaciΓ³n: Este proyecto usa Prisma ORM
El schema de Prisma estΓ‘ en: src/infrastructure/database/prisma/schema.prisma
Prisma Studio es una interfaz grΓ‘fica oficial de Prisma que te permite ver y editar datos directamente.
# Iniciar Prisma Studio
npm run prisma:studio
# Esto abrirΓ‘ automΓ‘ticamente en tu navegador:
# http://localhost:5555CaracterΓsticas:
- β Interfaz visual moderna
- β Ver todas las tablas (Account, Campaign)
- β Editar datos directamente
- β Filtrar y buscar registros
- β Crear nuevos registros
- β Ver relaciones entre tablas
# Conectar a la base de datos usando psql
# Windows (si tienes PostgreSQL instalado):
psql -U postgres -d meta_backend -h localhost -p 5432
# O desde Docker:
docker exec -it campaignflow-postgres psql -U postgres -d meta_backend
# Comandos ΓΊtiles:
\dt # Listar todas las tablas
\d Account # Ver estructura de la tabla Account
\d Campaign # Ver estructura de la tabla Campaign
SELECT * FROM "Account"; # Ver todos los registros de Account
SELECT * FROM "Campaign"; # Ver todos los registros de Campaign
\q # SalirpgAdmin es una herramienta grΓ‘fica completa para PostgreSQL.
- Descarga desde: https://www.pgadmin.org/download/
- Instala pgAdmin 4
- Crea una nueva conexiΓ³n:
- Host:
localhost - Port:
5432 - Database:
meta_backend - Username:
postgres - Password:
postgres
- Host:
DBeaver es un cliente SQL universal que funciona con mΓΊltiples bases de datos.
- Descarga desde: https://dbeaver.io/download/
- Instala DBeaver Community Edition
- Crea una nueva conexiΓ³n PostgreSQL:
- Host:
localhost - Port:
5432 - Database:
meta_backend - Username:
postgres - Password:
postgres
- Host:
TablePlus es una herramienta moderna y elegante para bases de datos.
- Descarga desde: https://tableplus.com/
- Instala TablePlus
- Crea una nueva conexiΓ³n PostgreSQL con las mismas credenciales
Tabla: Account
id: String (UUID o acct1-acct5)name: String (max 255 caracteres)createdAt: DateTimeupdatedAt: DateTime- RelaciΓ³n: Un Account tiene muchas Campaigns (1:N)
Tabla: Campaign
id: String (UUID)name: String (max 255 caracteres)status: CampaignStatus (ACTIVE, PAUSED, DELETED, ARCHIVED)spend: Decimal(10,2) - Gasto actualbudget: Decimal(10,2) - PresupuestoaccountId: String (Foreign Key a Account)createdAt: DateTimeupdatedAt: DateTime- Γndices:
accountId,status
-- Ver todas las cuentas
SELECT * FROM "Account" ORDER BY "createdAt" DESC;
-- Ver todas las campaΓ±as con su cuenta
SELECT
c.id,
c.name,
c.status,
c.spend,
c.budget,
a.name AS account_name
FROM "Campaign" c
JOIN "Account" a ON c."accountId" = a.id
ORDER BY c."createdAt" DESC;
-- Contar campaΓ±as por cuenta
SELECT
a.id,
a.name,
COUNT(c.id) AS total_campaigns,
SUM(c.spend) AS total_spend,
SUM(c.budget) AS total_budget
FROM "Account" a
LEFT JOIN "Campaign" c ON a.id = c."accountId"
GROUP BY a.id, a.name;
-- Ver campaΓ±as activas
SELECT * FROM "Campaign"
WHERE status = 'ACTIVE'
ORDER BY spend DESC;# Generar Prisma Client (despuΓ©s de cambiar schema)
npm run prisma:generate
# Crear nueva migraciΓ³n (despuΓ©s de cambiar schema)
npm run prisma:migrate
# Aplicar migraciones en producciΓ³n
npm run prisma:migrate:deploy
# Abrir Prisma Studio (GUI visual)
npm run prisma:studio
# Ejecutar seed (poblar datos iniciales)
npm run prisma:seed
# Ver formato del schema
npx prisma format --schema=./src/infrastructure/database/prisma/schema.prisma
# Validar schema
npx prisma validate --schema=./src/infrastructure/database/prisma/schema.prismaPara mΓ‘s detalles, consulta la guΓa completa en GUIA_DATABASE.md
To build and run in production mode:
# Build the project
npm run build
# Start production server
npm run startOnce the server is running, access the Swagger documentation at:
http://localhost:3000/docs
GET /accounts- Get all accountsPOST /accounts- Create a new accountPOST /accounts/sync- Sync accounts from external Meta Ads API
POST /accounts/:id/campaigns/sync- Sync campaigns for a specific accountGET /accounts/:id/campaigns/metrics- Get campaign metrics for an accountPOST /sync/all- Sync all campaigns for all accounts concurrently
GET /health- Health check endpoint
npm run dev- Start development server with hot reloadnpm run build- Build the projectnpm run start- Start production servernpm run test- Run all testsnpm run test:unit- Run unit tests onlynpm run test:integration- Run integration tests onlynpm run prisma:generate- Generate Prisma clientnpm run prisma:migrate- Run database migrationsnpm run prisma:migrate:deploy- Deploy migrations in productionnpm run prisma:studio- Open Prisma Studio (database GUI)npm run prisma:seed- Seed database with initial data
The project uses Vitest for testing. Tests are organized in:
tests/unit/- Unit tests for use cases and servicestests/integration/- Integration tests for API endpointstests/mocks/- Mock utilities for testing
Run tests with:
# Run all tests
npm test
# Run unit tests only
npm run test:unit
# Run integration tests only
npm run test:integration
# Run tests with coverage
npm test -- --coverageThe project includes Docker support for easy deployment and development.
- Docker Engine 20.10+
- Docker Compose 2.0+
- Copy environment variables
cp .env.example .env- Configure your .env file with secure credentials
IMPORTANT: Replace the placeholder values with strong, unique passwords:
# Use strong passwords in production!
POSTGRES_USER=your_secure_username
POSTGRES_PASSWORD=your_secure_password_here
POSTGRES_DB=meta_backend
DATABASE_URL="postgresql://your_secure_username:your_secure_password_here@localhost:5432/meta_backend"- Start services with Docker Compose
docker-compose up -dThis will start:
- PostgreSQL database on port
5432 - Fastify API server on port
3000
Security Note: The docker-compose.yml file requires environment variables to be set. Never commit real credentials to version control.
- Run database migrations
docker-compose exec app npx prisma migrate deploy --schema=./src/infrastructure/database/prisma/schema.prismaOr use the npm script:
docker-compose exec app npm run prisma:migrate:deploy- Access the application
- API: http://localhost:3000
- Swagger Docs: http://localhost:3000/docs
- Health Check: http://localhost:3000/health
# Start services in detached mode
docker-compose up -d
# View logs
docker-compose logs -f app
# Stop services
docker-compose down
# Stop services and remove volumes
docker-compose down -v
# Rebuild containers
docker-compose build
# Execute commands in container
docker-compose exec app npm run prisma:generate
docker-compose exec app npm run prisma:migrate
# Open Prisma Studio (database GUI)
docker-compose exec app npm run prisma:studioThe following environment variables must be configured in .env (they are required, no defaults are provided for security):
# Database Configuration (REQUIRED - use strong passwords!)
POSTGRES_USER=your_secure_username
POSTGRES_PASSWORD=your_secure_password
POSTGRES_DB=meta_backend
POSTGRES_PORT=5432
DATABASE_URL="postgresql://your_secure_username:your_secure_password@localhost:5432/meta_backend"
# Application Configuration
PORT=3000
NODE_ENV=productionSecurity Best Practices:
- Use strong, unique passwords (minimum 16 characters, mix of letters, numbers, and symbols)
- Never commit
.envfiles to version control (already in.gitignore) - Use different credentials for development and production
- Rotate passwords regularly
- Consider using secrets management tools (AWS Secrets Manager, HashiCorp Vault, etc.) in production
For production deployment, ensure:
- Use strong database passwords
- Set
NODE_ENV=production - Configure proper database connection strings
- Use environment-specific
.envfiles - Enable proper logging and monitoring
If you encounter errors about Prisma schema not being found, ensure you're using the npm scripts which include the --schema flag:
npm run prisma:generate
npm run prisma:migrateIf you see Cannot find module 'dist/main.js' error:
- Make sure you've built the project first:
npm run build- Verify the
distfolder exists and contains the compiled files
- Start PostgreSQL with Docker (if not installed locally):
docker-compose up -d postgres- Verify your
.envfile has the correctDATABASE_URL. For Docker, it should be:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/meta_backend"Common mistake: If you see Authentication failed for user 'user', your .env has incorrect credentials. Make sure you're using postgres:postgres (not user:password).
-
Ensure PostgreSQL is running and accessible:
- Check Docker:
docker-compose ps - Check locally:
psql -U postgres -l
- Check Docker:
-
Wait a few seconds after starting PostgreSQL before running migrations
If port 3000 is already in use:
-
Stop the running process:
- Windows:
taskkill /F /IM node.exe(stops all Node processes) ornetstat -ano | findstr :3000thentaskkill /PID <pid> /F - Linux/Mac:
lsof -ti:3000 | xargs kill
- Windows:
-
Or change the
PORTin your.envfile
If you get P1002: The database server timed out:
- Check if another migration is running: Close any other terminals running Prisma commands
- Restart PostgreSQL:
docker-compose restart postgres
- Wait a few seconds before retrying the migration
- If the problem persists, check for locked connections:
docker-compose exec postgres psql -U postgres -d meta_backend -c "SELECT * FROM pg_locks WHERE NOT granted;"
If you see FST_ERR_INSTANCE_ALREADY_LISTENING:
- Stop the server completely: Press
Ctrl+Cin the terminal - Kill any remaining Node processes:
taskkill /F /IM node.exe(Windows) - Restart the server:
npm run dev - This error usually happens when
tsx watchrestarts the server while it's still running
Este proyecto implementa Hexagonal Architecture (tambiΓ©n conocida como Ports & Adapters), separando el dominio del negocio de los detalles de implementaciΓ³n.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β API Layer (Fastify) β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Controllers β β Routes β β Schemas β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Application Layer (Use Cases) β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β SyncAccounts β βSyncCampaigns β β GetMetrics β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Domain Layer (Core) β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Entities β β Repositories β β Errors β β
β β (Interfaces)β β (Interfaces) β β (Custom) β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Infrastructure Layer (Adapters) β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Prisma β β HTTP Client β β Config β β
β β Repositories β β (Meta API) β β (Fastify) β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ΒΏPor quΓ© Fastify?
- Performance: Fastify es significativamente mΓ‘s rΓ‘pido que Express (2-3x en benchmarks)
- TypeScript First: Mejor soporte nativo para TypeScript
- Schema Validation: ValidaciΓ³n automΓ‘tica de schemas JSON con JSON Schema
- Plugin System: Arquitectura de plugins mΓ‘s robusta y modular
- Logging: Pino integrado para logging estructurado de alta performance
Trade-offs:
- Menor ecosistema que Express (pero suficiente para este proyecto)
- Curva de aprendizaje ligeramente mayor
ΒΏPor quΓ© Prisma?
- Type Safety: Genera tipos TypeScript automΓ‘ticamente desde el schema
- Developer Experience: Migraciones automΓ‘ticas, Prisma Studio, mejor DX
- Performance: Query builder optimizado, conexiones eficientes
- Schema como Single Source of Truth: Un solo archivo define estructura y tipos
- Moderno: Sintaxis intuitiva, menos boilerplate
Trade-offs:
- Menos flexible que TypeORM para queries complejas (pero suficiente para este caso)
- Learning curve para developers no familiarizados
ΒΏPor quΓ© esta arquitectura?
- Testabilidad: Cada capa puede testearse independientemente
- Mantenibilidad: Cambios en infraestructura no afectan lΓ³gica de negocio
- Escalabilidad: FΓ‘cil agregar nuevos adaptadores (REST, GraphQL, gRPC)
- Desacoplamiento: El dominio no depende de frameworks externos
- Clean Code: SeparaciΓ³n clara de responsabilidades
Trade-offs:
- MΓ‘s archivos y estructura inicial (pero vale la pena para proyectos grandes)
- Requiere mΓ‘s disciplina del equipo
ΒΏPor quΓ© TypeScript?
- Type Safety: Detecta errores en tiempo de compilaciΓ³n
- Autocomplete: Mejor experiencia de desarrollo con IDE
- Refactoring Seguro: Cambios masivos con confianza
- DocumentaciΓ³n ImplΓcita: Los tipos documentan el cΓ³digo
ΒΏPor quΓ© DI manual en lugar de librerΓa (InversifyJS, TSyringe)?
- Simplicidad: No necesitamos decoradores complejos para este proyecto
- Control Total: Sabemos exactamente quΓ© se estΓ‘ inyectando
- Menos Dependencias: Menos librerΓas externas
- Suficiente: Para el tamaΓ±o del proyecto, DI manual es suficiente
Trade-offs:
- Si el proyecto crece mucho, podrΓa beneficiarse de una librerΓa DI
- MΓ‘s cΓ³digo manual para mantener
ΒΏPor quΓ© Zod?
- Type Inference: Genera tipos TypeScript desde schemas
- Runtime Validation: Valida datos en runtime, no solo en compile-time
- Composable: Schemas pueden combinarse y reutilizarse
- Mensajes de Error: Mensajes claros y ΓΊtiles
- Lightweight: LibrerΓa pequeΓ±a y rΓ‘pida
ΒΏPor quΓ© Axios + axios-retry?
- Retry AutomΓ‘tico: Reintenta automΓ‘ticamente en errores transitorios
- Exponential Backoff: Evita sobrecargar API externa
- Interceptors: Facilita manejo centralizado de errores
- TypeScript Support: Buen soporte para TypeScript
ΒΏPor quΓ© locks in-memory en lugar de Redis?
- Simplicidad: No requiere infraestructura adicional para desarrollo
- Suficiente: Para una sola instancia, funciona perfectamente
- Extensible: FΓ‘cil migrar a Redis cuando se necesite escalar horizontalmente
Trade-off:
- No funciona en mΓΊltiples instancias (pero se documenta cΓ³mo migrar a Redis)
ΒΏPor quΓ© Decimal?
- PrecisiΓ³n: Float tiene problemas de precisiΓ³n con decimales (0.1 + 0.2 β 0.3)
- Moneda: Para valores monetarios, precisiΓ³n es crΓtica
- Prisma Support: Prisma soporta Decimal nativamente
Trade-off:
- Ligeramente mΓ‘s verboso en cΓ³digo (necesita
.toNumber())
ΒΏPor quΓ© Vitest?
- Velocidad: MΓ‘s rΓ‘pido que Jest
- TypeScript Native: Mejor integraciΓ³n con TypeScript
- Compatible con Jest API: FΓ‘cil migraciΓ³n si alguien viene de Jest
- Ecosystem: Compatible con herramientas de Jest
- Repository Pattern: Abstrae acceso a datos, permite cambiar ORM sin afectar lΓ³gica
- Use Case Pattern: Encapsula operaciones de negocio especΓficas
- Dependency Injection: Facilita testing y desacoplamiento
- Strategy Pattern: LockService puede cambiar implementaciΓ³n (memory β Redis)
- Error Handling con Clases: JerarquΓa de errores para manejo consistente
- Domain Layer (
src/domain/): Entidades, interfaces de repositorios, errores personalizados - Application Layer (
src/application/): Casos de uso, servicios de aplicaciΓ³n - Infrastructure Layer (
src/infrastructure/): Implementaciones (Prisma, HTTP, config) - API Layer (
src/api/): Controladores, rutas, validaciΓ³n de schemas
Juan Facundo Bazan Alvarez
Sr Backend Developer Node | Software Architect
Este proyecto fue desarrollado con arquitectura hexagonal limpia, desacoplada y lista para producciΓ³n.
Β© 2025 Juan Facundo Bazan Alvarez. Todos los derechos reservados.