TypeORM Integration
@MTEntity() decorator
Apply @MTEntity() before @Entity() on any TypeORM entity class that should be tenant-scoped. The decorator stores metadata that TenantEntitySubscriber and TenantBaseRepository read at runtime. It does not register TypeORM hooks directly.
import { MTEntity } from 'nestjs-mtenant';
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
// Default fields: tenantField='tenant', idField='id'
@MTEntity()
@Entity()
export class User {
@PrimaryGeneratedColumn() id: number;
@Column() tenant: string;
@Column() username: string;
}
// Custom field names
@MTEntity({ tenantField: 'org_id', idField: 'uuid' })
@Entity()
export class Invoice {
@PrimaryGeneratedColumn('uuid') uuid: string;
@Column() org_id: string;
@Column() amount: number;
}
TenantEntitySubscriber
TenantEntitySubscriber handles write operations by intercepting TypeORM entity events and setting the tenant field before the database operation executes.
Covered events
| Event | Behaviour |
|---|---|
beforeInsert |
Sets tenant on the entity if not already set |
beforeUpdate |
Sets tenant on the entity if not already set |
beforeRemove |
Sets tenant on the entity if not already set |
beforeSoftRemove |
Sets tenant on the entity if not already set |
The subscriber is registered automatically when you pass dataSource to MTModule.forRoot(). You can also register it manually:
import { TenantEntitySubscriber } from 'nestjs-mtenant';
dataSource.subscribers.push(new TenantEntitySubscriber());
TenantBaseRepository
TypeORM’s subscriber API does not intercept read queries. Use TenantBaseRepository as a drop-in replacement for Repository<T> to get automatic tenant filtering on every find operation.
Constructor
const repo = new TenantBaseRepository<User>(User, dataSource);
Read methods (tenant-filtered automatically)
| Method | Description |
|---|---|
find(options?) |
repository.find() with tenant where injected |
findOne(options) |
repository.findOne() with tenant where injected |
findAndCount(options?) |
repository.findAndCount() with tenant where injected |
findBy(where) |
repository.findBy() with tenant clause merged |
findOneBy(where) |
repository.findOneBy() with tenant clause merged |
count(options?) |
repository.count() with tenant where injected |
createQueryBuilder(alias?) |
Returns a QueryBuilder with .andWhere(tenant = ?) already applied |
When allowMissingTenant: true, the injected clause is WHERE (tenant = :t OR tenant IS NULL).
Write methods (delegate to underlying repository)
| Method | Description |
|---|---|
save(entity) |
Delegates to repository.save() — subscriber sets tenant |
remove(entity) |
Delegates to repository.remove() |
softRemove(entity) |
Delegates to repository.softRemove() |
rawRepository escape hatch
Access the underlying unfiltered Repository<T> for admin operations or when you need to bypass tenancy entirely:
const rawRepo = repo.rawRepository;
const allTenants = await rawRepo.find(); // no tenant filter
Example usage in a service
import { Injectable } from '@nestjs/common';
import { TenantBaseRepository } from 'nestjs-mtenant';
import { User } from './entities/user.entity';
import { dataSource } from './data-source';
@Injectable()
export class UsersService {
private readonly repo = new TenantBaseRepository<User>(User, dataSource);
findAll(): Promise<User[]> {
return this.repo.find();
}
findOne(id: number): Promise<User | null> {
return this.repo.findOne({ where: { id } });
}
create(dto: Partial<User>): Promise<User | User[]> {
const user = this.repo.rawRepository.create(dto);
return this.repo.save(user as User); // subscriber injects tenant
}
}
Tenant storage entity
To manage registered tenants in a TypeORM database, import and register TenantsStorageTypeOrmEntity:
import { TenantsStorageTypeOrmEntity } from 'nestjs-mtenant';
// Add to your DataSource entities array
const dataSource = new DataSource({
entities: [User, Book, TenantsStorageTypeOrmEntity],
// ...
});
Then pass it to MTModule:
MTModule.forRoot({
for: [User, Book],
storage: TYPEORM_STORAGE,
storageRepository: TenantsStorageTypeOrmEntity,
dataSource,
})
The entity maps to the tenants_storage table with columns: id, tenant (unique), settings (text), createdAt, updatedAt, deletedAt (soft-delete).
Limitations
These are important differences from the Sequelize integration.
No automatic read filtering without TenantBaseRepository. Plain dataSource.getRepository(User).find() does NOT add a tenant filter. You must use TenantBaseRepository or add the where clause manually.
QueryBuilder update / delete / insert bypass the subscriber. The TenantEntitySubscriber only fires on entity-based operations. Raw QueryBuilder mutations do not trigger it:
// Tenant NOT injected — use explicit where clause
await dataSource.createQueryBuilder()
.update(User)
.set({ username: 'new' })
.where('id = :id', { id: 1 })
.execute();
Nested relation filtering is not automatic. Unlike Sequelize, TenantBaseRepository.find({ relations: ['books'] }) does not add a tenant filter on the books side. Query each relation explicitly or use createQueryBuilder with manual joins.
Per-query disable. There is no { disableTenancy: true } option for TypeORM. Use rawRepository for unscoped access, or call disableTenancyForCurrentScope() on MTService to disable tenancy for the entire request scope.
// Option 1: bypass via rawRepository
const all = await repo.rawRepository.find();
// Option 2: disable for current scope (all entities, all operations)
this.mtService.disableTenancyForCurrentScope();