Loading home…
Loading home…
Loading article…
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

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.

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.

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.
Existe un momento en toda aplicación SaaS exitosa donde aparece una pregunta incómoda:
¿Cómo agregamos un nuevo cliente sin desplegar otra aplicación?
Al principio todo parece sencillo.
Tienes una API.
Tienes una base de datos.
Tienes un único cliente.
La arquitectura funciona perfectamente.
Luego llega un segundo cliente.
Después un tercero.
Y eventualmente alguien propone algo como:
Cliente A -> Base de datos A
Cliente B -> Base de datos B
Cliente C -> Base de datos C
Al principio parece una solución razonable.
Meses después descubres que cada despliegue, migración o cambio de esquema debe ejecutarse múltiples veces.
El mantenimiento comienza a convertirse en una pesadilla.
El problema no es la cantidad de clientes.
El problema es la arquitectura.
Multi-Tenant significa que múltiples organizaciones utilizan la misma plataforma mientras sus datos permanecen completamente aislados.
Por ejemplo:
Empresa A
├── Usuarios
├── Facturas
└── Inventario
Empresa B
├── Usuarios
├── Facturas
└── Inventario
Ambas empresas utilizan exactamente la misma aplicación.
La diferencia está en los datos.
Cada solicitud debe ejecutarse dentro del contexto correcto.
Muchas implementaciones comienzan agregando:
companyId
tenantId
organizationId
a todas las tablas.
@Entity('users')
export class UserEntity {
id: string;
tenantId: string;
email: string;
}
Esto funciona.
Hasta que alguien olvida filtrar:
WHERE tenant_id = ?
Y de repente un cliente puede visualizar información de otro.
Ese es uno de los errores más costosos que puede sufrir una plataforma SaaS.
No deberíamos preguntarnos:
¿Cómo filtramos los tenants?
Deberíamos preguntarnos:
¿Cómo garantizamos que nunca olvidaremos filtrar un tenant?
La diferencia es enorme.
Lo primero que necesita una aplicación multi-tenant es descubrir quién realiza la solicitud.
Existen varias estrategias.
acme.miapp.com
globex.miapp.com
X-Tenant-Id: acme
{
"sub": "123",
"tenantId": "acme"
}
En la mayoría de los proyectos modernos, JWT suele ser la opción más práctica.
Una vez identificado el tenant, debemos almacenarlo durante toda la solicitud.
Por ejemplo:
export interface TenantContext {
tenantId: string;
}
Middleware:
@Injectable()
export class TenantMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
req['tenantId'] = extractTenant(req);
next();
}
}
A partir de este momento todos los módulos pueden acceder al contexto actual.
Aquí aparece una de las decisiones más importantes.
Database
├── users
├── orders
└── invoices
Cada tabla contiene tenantId.
Ventajas:
Desventajas:
Tenant A -> Database A
Tenant B -> Database B
Tenant C -> Database C
Ventajas:
Desventajas:
Muchas plataformas modernas utilizan:
Plan Starter
-> Shared Database
Plan Enterprise
-> Dedicated Database
Esto permite optimizar costos sin sacrificar flexibilidad.
Cuando utilizamos bases de datos dedicadas, la aplicación debe resolver dinámicamente la conexión.
const connection =
await tenantConnectionFactory.getConnection(
tenantId,
);
A partir de aquí los repositorios trabajan utilizando la conexión correcta.
El dominio nunca necesita conocer cómo se resolvió.
Aquí es donde la arquitectura comienza a mostrar su valor.
Los casos de uso no deberían saber nada sobre:
Los handlers trabajan normalmente:
await userRepository.create(user);
La infraestructura decide qué conexión utilizar.
Esto mantiene el dominio limpio y desacoplado.
La seguridad en sistemas multi-tenant no es opcional.
Siempre valida:
Nunca confíes únicamente en información enviada por el cliente.
| Beneficio | Impacto |
|---|---|
| Escalabilidad | Nuevos clientes sin nuevas aplicaciones |
| Menor costo | Infraestructura compartida |
| Seguridad | Aislamiento de datos |
| Mantenibilidad | Menos despliegues |
| Flexibilidad | Soporte para distintos planes |
Muchas empresas creen que construir una aplicación SaaS consiste en agregar usuarios.
En realidad consiste en administrar aislamiento.
Si la incorporación de un nuevo cliente requiere crear otra aplicación, duplicar repositorios o desplegar nuevos servicios, probablemente tu arquitectura aún no sea verdaderamente multi-tenant.
Una buena implementación permite agregar organizaciones, cambiar planes, escalar infraestructura y mantener aislamiento sin modificar la lógica de negocio.
Y cuando el dominio deja de preocuparse por los tenants, la plataforma comienza a escalar de verdad.