Cómo construir una aplicación SaaS Multi-Tenant en NestJS sin duplicar tu código
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

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.
¿Qué significa Multi-Tenant?
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.
El error más común
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.
La pregunta correcta
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.
Tenant Resolution
Lo primero que necesita una aplicación multi-tenant es descubrir quién realiza la solicitud.
Existen varias estrategias.
Subdominios
acme.miapp.com
globex.miapp.com
Headers
X-Tenant-Id: acme
JWT
{
"sub": "123",
"tenantId": "acme"
}
En la mayoría de los proyectos modernos, JWT suele ser la opción más práctica.
Creando el Tenant Context
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.
Shared Database vs Dedicated Database
Aquí aparece una de las decisiones más importantes.
Shared Database
Database
├── users
├── orders
└── invoices
Cada tabla contiene tenantId.
Ventajas:
- Menor costo.
- Menor complejidad operativa.
- Más fácil de administrar.
Desventajas:
- Menor aislamiento.
- Riesgo de errores de filtrado.
Dedicated Database
Tenant A -> Database A
Tenant B -> Database B
Tenant C -> Database C
Ventajas:
- Máximo aislamiento.
- Mayor seguridad.
Desventajas:
- Costos más altos.
- Migraciones más complejas.
- Operación más costosa.
El modelo híbrido
Muchas plataformas modernas utilizan:
Plan Starter
-> Shared Database
Plan Enterprise
-> Dedicated Database
Esto permite optimizar costos sin sacrificar flexibilidad.
Resolución dinámica de conexiones
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ó.
Multi-Tenancy y Arquitectura Hexagonal
Aquí es donde la arquitectura comienza a mostrar su valor.
Los casos de uso no deberían saber nada sobre:
- PostgreSQL
- MongoDB
- Tenant Resolution
- Connection Factories
Los handlers trabajan normalmente:
await userRepository.create(user);
La infraestructura decide qué conexión utilizar.
Esto mantiene el dominio limpio y desacoplado.
Seguridad
La seguridad en sistemas multi-tenant no es opcional.
Siempre valida:
- Tenant del JWT.
- Tenant del recurso.
- Tenant de la conexión.
Nunca confíes únicamente en información enviada por el cliente.
Lo que ganamos en la práctica
| Beneficio | Impacto |
|---|---|
| Escalabilidad | Nuevos clientes sin nuevas aplicaciones |
| Menor costo | Infraestructura compartida |
| Seguridad | Aislamiento de datos |
| Mantenibilidad | Menos despliegues |
| Flexibilidad | Soporte para distintos planes |
Para cerrar
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.
Recibe artículos por email
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.
Artículos relacionados

Tu API no necesita más servicios, necesita eventos
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.

CQRS en NestJS: deja de mezclar lecturas y escrituras en el mismo servicio
Cuando tu UserService atiende POST y GET, optimizar un lado rompe el otro. Comandos, consultas y handlers con @nestjs/cqrs, sin humo.

Por qué tu servicio NestJS se vuelve inmantenible (y cómo la arquitectura hexagonal lo arregla)
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.