Loading home…
Loading home…
Loading article…
Controladores que saben demasiado, entidades llenas de decoradores ORM y tests que exigen base de datos. Guía práctica de puertos y adaptadores en NestJS.

Sin spam — solo un aviso cuando publique algo nuevo sobre backend, cloud y arquitectura.
Un email cuando salga un artículo. Puedes darte de baja cuando quieras.

Cuando tu UserService atiende POST y GET, optimizar un lado rompe el otro. Comandos, consultas y handlers con @nestjs/cqrs, sin humo.

Si cada nueva funcionalidad te obliga a modificar cinco servicios distintos, probablemente tengas un problema de acoplamiento. Aprende cómo Event-Driven Architecture ayuda a desacoplar módulos y escalar aplicaciones NestJS.

Si agregar un nuevo cliente implica desplegar una nueva aplicación o copiar una base de datos completa, probablemente tu arquitectura SaaS no está preparada para escalar. Aprende cómo implementar multi-tenancy en NestJS de forma limpia y mantenible
Empiezas un proyecto NestJS con buenas intenciones. Seis meses después, users.service.ts importa TypeORM, envía correos, hashea contraseñas y publica eventos en Kafka. Cambiar la base de datos parece una operación a corazón abierto.
No es un problema de equipo. Es un problema de arquitectura — y la arquitectura hexagonal (puertos y adaptadores) es una de las respuestas más efectivas cuando ya no alcanza el clásico controller → service → entity.
Este artículo sigue el modelo del NestJS Enterprise Starter, con el nivel de detalle para aplicarlo en tu propio código esta semana.
Cuando la lógica de negocio, HTTP y persistencia viven en la misma clase:
@Post().La arquitectura hexagonal traza una frontera: el centro son tus reglas de negocio; todo lo demás es un plugin reemplazable.
Imagina tu aplicación como un hexágono:
HTTP → Application (casos de uso) → Dominio
↑
El repositorio TypeORM implementa UserRepositoryPort
La regla es simple: las dependencias apuntan hacia adentro. core/ nunca importa @nestjs/common ni typeorm.
src/
├── core/
│ ├── domain/
│ └── ports/output/persistence/
├── application/
│ └── commands/user/
├── adapters/
│ ├── primary/http/
│ └── secondary/persistence/typeorm/
└── modules/user/
Los módulos NestJS son solo cableado: enlazan UserRepositoryPort → UserRepository. Los handlers dependen del puerto; el módulo elige el adaptador.
El dominio declara lo que necesita:
export abstract class UserRepositoryPort {
abstract findByEmail(email: string): Promise<User | null>;
abstract create(payload: CreateUserPayload): Promise<User>;
}
Sin @Injectable(), sin Repository<UserOrmEntity>. El handler pide una capacidad; NestJS inyecta la implementación en runtime.
La entidad TypeORM queda en adapters/secondary:
@Entity('users')
export class UserOrmEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
email: string;
// ...
}
Un mapper traduce ORM → dominio:
static toDomain(orm: UserOrmEntity): User {
return { id: orm.id, email: orm.email, name: orm.name, ... };
}
Los handlers nunca ven UserOrmEntity. Si migras a Prisma, reescribes un adaptador — no cuarenta casos de uso.
Mal:
@Post()
async create(@Body() dto: CreateUserDto) {
const hash = await bcrypt.hash(dto.password, 10);
return this.repo.save({ ...dto, password: hash });
}
Bien:
@Post()
create(@Body() dto: CreateUserDto) {
return this.commandBus.execute(new CreateUserCommand(dto));
}
Reglas de contraseña, emails duplicados y correos de bienvenida van en un command handler en la capa de aplicación.
| Beneficio | Cómo se siente en la práctica |
|---|---|
| Tests rápidos | Handlers con mocks jest.fn() — sin Docker |
| Infra intercambiable | Nueva cola o BD = nuevo adaptador + provider |
| Onboarding | Todos saben dónde va cada pieza |
| Reutilización | El mismo handler desde HTTP, cron o worker BullMQ |
core/ — muévelas a adapters; mapea a tipos planos.UserRepository concreto en handlers — inyecta UserRepositoryPort.Este diseño encaja con CQRS (lecturas y escrituras separadas) y con puertos para colas (BullMQ detrás de IQueueService). En esta serie cubrimos esos patrones con el mismo criterio de dependencias.
Si estás construyendo una API NestJS que debe sobrevivir más de un ciclo de contratación, empieza sacando un módulo (Users) a puertos y adaptadores. La diferencia se nota en la primera semana de tests.