Package Exports
- nestjs-multitenant
- nestjs-multitenant/package.json
Readme
nestjs-multitenant
Una solución completa de multi-tenancy para aplicaciones NestJS con arquitectura de esquema por tenant (PostgreSQL) y utilidades para inyección de repositorios, middlewares de resolución de tenant y configuración tipada.
📖 Read the Full Documentation - Comprehensive guides, API reference, and examples
🚀 Características
- Arquitectura Schema-per-Tenant: Cada tenant tiene su propio esquema de base de datos
- Resolución Automática de Tenants: Soporte para múltiples estrategias (header, subdomain, JWT, custom)
- Pool de Conexiones Dinámico: Gestión eficiente de conexiones por tenant
- Inyección de Repositorios: Decoradores para inyectar repositorios específicos del tenant
- Administración de Tenants: Módulo completo para CRUD de tenants
- Registro de Entidades: Sistema flexible para configurar entidades por tenant
- TypeScript: Completamente tipado con soporte completo de TypeScript
- Escalable: Diseñado para aplicaciones de gran escala
Instalación
Instala el paquete y sus peer dependencies requeridas:
pnpm add nestjs-multitenantDependencias Peer
npm install @nestjs/common @nestjs/core @nestjs/typeorm @nestjs/config typeorm pgRequisitos: Node.js >= 22, TypeScript >= 5.9, NestJS 11
🛠️ Configuración Básica
1. Configurar el Módulo Principal (forRoot)
Importa el módulo en tu aplicación e inicializa la configuración:
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { MultiTenantModule } from 'nestjs-multitenant';
@Module({
imports: [
// ⚠️ IMPORTANTE: ConfigModule debe importarse ANTES que MultiTenantModule
ConfigModule.forRoot({
isGlobal: true, // Hace que ConfigService esté disponible globalmente
}),
// Configuración de la base de datos principal
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'password',
database: 'multitenant_db',
schema: 'public',
synchronize: true,
}),
// Configuración del módulo multi-tenant
MultiTenantModule.forRoot({
database: {
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'password',
database: 'multitenant_db',
},
autoCreateSchemas: true,
enableAdminModule: true, // Habilita el módulo de administración
platform: 'express', // express o fastify
}),
],
})
export class AppModule {}2. Configuración Asíncrona (forRootAsync)
Para configuraciones más complejas que requieren inyección de dependencias:
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import {
MultiTenantModule,
createDatabaseConfigFromEnv,
} from 'nestjs-multitenant';
@Module({
imports: [
// ConfigModule DEBE ser importado ANTES que MultiTenantModule
ConfigModule.forRoot({
isGlobal: true,
}),
// Conexión principal de la aplicación
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'multitenant_db',
schema: 'public',
synchronize: true,
}),
// Configuración asíncrona del módulo multi-tenant
MultiTenantModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
database: createDatabaseConfigFromEnv(configService),
autoCreateSchemas: configService.get<boolean>(
'AUTO_CREATE_SCHEMAS',
true,
),
enableAdminModule: configService.get<boolean>(
'ENABLE_ADMIN_MODULE',
true,
),
platform: configService.get<string>(
'PLATFORM',
'express',
) as PlatformType, // express o fastify de .env
}),
}),
],
})
export class AppModule {}Nota importante sobre forRootAsync:
- Siempre incluye las funcionalidades de administración (TenantAdminService, TenantAdminController)
- La opción
enableAdminModulecontrola si estas funcionalidades están activas en tiempo de ejecución - La configuración de base de datos admin se toma directamente de las variables de entorno (no del parámetro
database) - Es ideal para configuraciones que dependen de variables de entorno o servicios externos
3. Registro de Entidades para forRootAsync
Cuando uses forRootAsync, es CRÍTICO registrar las entidades ANTES de que se inicialice el módulo:
// entities/index.ts - Crear este archivo PRIMERO
import { EntityRegistry } from 'nestjs-multitenant';
import { User } from './user.entity';
import { Product } from './product.entity';
// IMPORTANTE: Registrar INMEDIATAMENTE al importar
// Opción 1: API fluida (recomendada)
EntityRegistry.getInstance()
.registerEntity('User', User)
.registerEntity('Product', Product);
// Opción 2: Registrar múltiples entidades
export const entities = {
User: User,
Product: Product,
};
EntityRegistry.getInstance().registerEntities(entities);// app.module.ts
import { Module } from '@nestjs/common';
import './entities'; // IMPORTAR PRIMERO para registrar entidades
import { MultiTenantModule } from 'nestjs-multitenant';
@Module({
imports: [
// ... otros módulos
MultiTenantModule.forRootAsync({
// ... configuración
}),
],
})
export class AppModule {}⚠️ Problema Común: Si registras las entidades después de la inicialización del módulo, getEntityRegistryConfig() retornará un objeto entities vacío.
✅ Solución: Siempre importa el archivo de registro de entidades ANTES que MultiTenantModule.forRootAsync.
🔍 Debug: Usa getEntityRegistryDebugInfo() para verificar el estado del registro:
import { getEntityRegistryDebugInfo } from 'nestjs-multitenant';
// En un endpoint o servicio
const debugInfo = getEntityRegistryDebugInfo();
console.log('Registry state:', debugInfo);
// Output: { entityCount: 2, entities: ['User', 'Product'], presets: ['basic', 'full'] }4. Variables de Entorno
# .env
DB_TYPE=postgres
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=password
DB_DATABASE=multitenant_db
TENANT_HEADER=x-tenant-id
AUTO_CREATE_SCHEMAS=true
ENABLE_ADMIN_MODULE=true🏗️ Uso del Sistema
Definir Entidades
// entities/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column({ unique: true })
email: string;
@Column()
createdAt: Date;
}Registrar Entidades
// entities/index.ts
import { EntityRegistry } from 'nestjs-multitenant';
import { User } from './user.entity';
import { Product } from './product.entity';
// Opción 1: Registrar entidades individualmente (API fluida)
EntityRegistry.getInstance()
.registerEntity('User', User)
.registerEntity('Product', Product)
.registerPreset('basic', ['User'])
.registerPreset('ecommerce', ['User', 'Product']);
// Opción 2: Registrar múltiples entidades a la vez
const entities = {
User: User,
Product: Product,
};
EntityRegistry.getInstance()
.registerEntities(entities)
.registerPresets({
basic: ['User'],
ecommerce: ['User', 'Product'],
});Crear un Servicio con Repositorios de Tenant
// services/user.service.ts
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectTenantRepository } from 'nestjs-multitenant';
import { User } from '../entities/user.entity';
@Injectable()
export class UserService {
constructor(
@InjectTenantRepository(User)
private readonly userRepository: Repository<User>,
) {}
async findAll(): Promise<User[]> {
return this.userRepository.find();
}
async create(userData: Partial<User>): Promise<User> {
const user = this.userRepository.create(userData);
return this.userRepository.save(user);
}
async findById(id: string): Promise<User> {
return this.userRepository.findOne({ where: { id } });
}
}Configurar un Módulo de Funcionalidad
// modules/user.module.ts
import { Module } from '@nestjs/common';
import { createTenantRepositoryProviders } from 'nestjs-multitenant';
import { User } from '../entities/user.entity';
import { UserService } from '../services/user.service';
import { UserController } from '../controllers/user.controller';
@Module({
providers: [...createTenantRepositoryProviders([User]), UserService],
controllers: [UserController],
exports: [UserService],
})
export class UserModule {}🔧 Configuración Avanzada
Estrategias de Resolución de Tenants
- Header: Extrae el tenant ID de un encabezado HTTP.
- Subdominio: Utiliza el subdominio como tenant ID.
- Dominio: Extrae el tenant ID del dominio.
- Query Parameter: Obtiene el tenant ID de un parámetro de consulta.
MultiTenantModule.forRootAsync({
tenantResolution: {
strategy: 'header' | 'subdomain' | 'jwt' | 'custom',
headerName: 'x-tenant-id', // Encabezado por defecto
jwtClaimName: 'tenantId', // Claim por defecto en JWT
customResolver: (request: unknown) => {
// Lógica personalizada para resolver tenant
return 'default-tenant';
},
},
});Nota Importante:
- Si la estrategia de resolución es
header, asegúrate de que el encabezado exista en la solicitud y definirheaderNamesi es diferente dex-tenant-id. - Si la estrategia de resolución es
subdomain, asegúrate de que el subdominio esté configurado correctamente. - Si la estrategia de resolución es
jwt, asegúrate de que el token JWT esté presente y válido; ademas, definirjwtClaimNamesi es diferente detenantId. - Si la estrategia de resolución es
custom, asegúrate de proporcionar una función personalizada que devuelva el tenant ID basado en la solicitud.
Pool de Conexiones
MultiTenantModule.forRoot({
// ... otras configuraciones
connectionPool: {
maxConnections: 50,
idleTimeout: 30000,
enableCleanup: 30000,
cleanupInterval: 5000,
},
});Configuración de Entidades por Tenant
// Configurar entidades disponibles por tenant
EntityRegistry.getInstance()
.registerPreset('startup', ['User', 'Project'])
.registerPreset('enterprise', ['User', 'Project', 'Analytics', 'Billing']);🔍 Uso Avanzado
Acceso Directo al DataSource del Tenant
import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { InjectTenantDataSource } from 'nestjs-multitenant';
@Injectable()
export class AdvancedService {
constructor(
@InjectTenantDataSource()
private readonly dataSource: DataSource,
) {}
async executeRawQuery(query: string) {
return this.dataSource.query(query);
}
async runTransaction(callback: (manager: EntityManager) => Promise<any>) {
return this.dataSource.transaction(callback);
}
}Factory de Repositorios
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectTenantRepositoryFactory } from 'nestjs-multitenant';
import { User } from '../entities/user.entity';
@Injectable()
export class MultiTenantService {
constructor(
@InjectTenantRepositoryFactory(User)
private readonly userRepositoryFactory: (
tenantId: string,
) => Promise<Repository<User>>,
) {}
async getUsersFromSpecificTenant(tenantId: string) {
const userRepository = await this.userRepositoryFactory(tenantId);
return userRepository.find();
}
}🔧 Resolución de Problemas
Error: "Nest can't resolve dependencies of the TypeOrmModuleOptions"
Problema: Error al inicializar el módulo con el mensaje:
UnknownDependenciesException [Error]: Nest can't resolve dependencies of the TypeOrmModuleOptions (?).
Please make sure that the argument ConfigService at index [0] is available in the TypeOrmCoreModule context.Solución: Este error ocurre cuando ConfigModule no está disponible en el contexto del módulo. Asegúrate de:
- Importar ConfigModule ANTES que MultiTenantModule:
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }), // ⚠️ DEBE ir ANTES
MultiTenantModule.forRoot({ /* config */ }),
],
})- Usar configuración global:
ConfigModule.forRoot({
isGlobal: true, // Hace ConfigService disponible globalmente
});- Para configuración asíncrona, inyectar ConfigService correctamente:
MultiTenantModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
// configuración
}),
});Error: "Cannot find module 'nestjs-multitenant'"
Solución: Instala las dependencias requeridas:
npm install @nestjs/common @nestjs/core @nestjs/typeorm @nestjs/config typeorm pgProblemas de Conexión a Base de Datos
Problema: Errores de conexión o timeout.
Solución: Verifica la configuración de la base de datos y asegúrate de que PostgreSQL esté ejecutándose:
MultiTenantModule.forRoot({
database: {
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'password',
database: 'multitenant_db',
// Opcional: configuración adicional
ssl: false,
synchronize: true, // Solo en desarrollo
logging: true, // Para debug
},
});🤝 Contribución
- Fork el proyecto
- Crea una rama para tu feature (
git checkout -b feature/amazing-feature) - Commit tus cambios (
git commit -m 'Add amazing feature') - Push a la rama (
git push origin feature/amazing-feature) - Abre un Pull Request
Versionado y CHANGELOG
Este proyecto sigue SemVer. Las releases se realizan con mensajes de
commit semánticos y se documentan en CHANGELOG.md.
🆘 Soporte
- 📧 Email: lmorochofebres@gmail.com
- 🐛 Issues: GitHub Issues
- 📖 Documentación: Docs
Licencia
MIT © Reymi Tech