Skip to content
← Back to blog
TypeORMPostgreSQLMulti-TenancyNestJS

TypeORM Multi-Tenancy with PostgreSQL

Firas Sayah·June 2, 2026·5 min read

Choosing a Multi-Tenancy Strategy

SaaS Starter includes a production-ready multi-tenancy implementation. See the multi-tenancy feature page.

Multi-tenancy is the foundation of any SaaS product. Your tenants share infrastructure but their data must remain isolated. With TypeORM and PostgreSQL, you have three options, each with clear trade-offs.

Strategy 1: Row-Level Isolation

The simplest approach. All tenants share one database and one schema. Every table has a tenantId column, and every query filters by it.

@Entity()
export class Project {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  tenantId: string;

  @Column()
  name: string;
}

Enforce tenant filtering globally with a subscriber:

@EventSubscriber()
export class TenantSubscriber implements EntitySubscriberInterface {
  beforeInsert(event: InsertEvent<any>) {
    if (event.entity && this.tenantContext.tenantId) {
      event.entity.tenantId = this.tenantContext.tenantId;
    }
  }
}

Pros: Simple migrations, easy to manage, lowest infrastructure cost. Cons: One missing WHERE clause leaks data across tenants. Noisy-neighbor risk on large tables.

Strategy 2: Schema-Level Isolation

Each tenant gets their own PostgreSQL schema within the same database. This provides stronger isolation while keeping a single database connection.

@Injectable({ scope: Scope.REQUEST })
export class TenantConnectionService {
  async getConnection(tenantId: string): Promise<DataSource> {
    const schema = `tenant_${tenantId}`;
    await this.dataSource.query(`SET search_path TO "${schema}"`);

<div class="not-prose my-8 rounded-xl border border-primary-200 dark:border-primary-800 bg-primary-50 dark:bg-primary-900/20 p-6">
<p class="text-base font-semibold text-primary-900 dark:text-primary-100 mb-1">Cloudrix SaaS Starter ships with this pre-configured and tested.</p>
<p class="text-sm text-primary-700 dark:text-primary-300 mb-3">Skip weeks of boilerplate — auth, payments, multi-tenancy, and deployment included out of the box.</p>
<a href="https://demo.cloudrix.io" class="inline-flex items-center text-sm font-medium text-primary-600 dark:text-primary-400 hover:text-primary-500">Try the live demo &rarr;</a>
</div>

    return this.dataSource;
  }
}

Run migrations per schema:

async migrateTenant(tenantId: string) {
  const schema = `tenant_${tenantId}`;
  await this.dataSource.query(`CREATE SCHEMA IF NOT EXISTS "${schema}"`);
  await this.dataSource.query(`SET search_path TO "${schema}"`);
  await this.dataSource.runMigrations();
}

Pros: Strong isolation, PostgreSQL enforces boundaries, shared connection pool. Cons: Schema count can grow large. Migrations must run per tenant.

Strategy 3: Database-Level Isolation

Each tenant gets a completely separate database. Maximum isolation, maximum overhead.

@Injectable()
export class TenantDatabaseService {
  private connections = new Map<string, DataSource>();

  async getConnection(tenantId: string): Promise<DataSource> {
    if (!this.connections.has(tenantId)) {
      const ds = new DataSource({
        type: 'postgres',
        database: `saas_${tenantId}`,
        // ... connection options
      });
      await ds.initialize();
      this.connections.set(tenantId, ds);
    }
    return this.connections.get(tenantId);
  }
}

Pros: Complete isolation, per-tenant backup and restore, no noisy neighbors. Cons: Connection pool per tenant, expensive at scale, complex deployment.

Row-Level Security with PostgreSQL Policies

For row-level tenancy, PostgreSQL's built-in Row-Level Security (RLS) adds a database-enforced safety net:

ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON projects
  USING (tenant_id = current_setting('app.tenant_id')::uuid);

Set the tenant context on each request:

async onModuleInit() {
  this.dataSource.query(`SET app.tenant_id = '${tenantId}'`);
}

Even if your application code has a bug, PostgreSQL will refuse to return rows belonging to other tenants.

Which Strategy Should You Pick?

For most early-stage SaaS products, row-level with RLS is the right choice. It keeps infrastructure simple and costs low while providing database-enforced isolation. Move to schema-level when you have enterprise customers who contractually require it.

Get Started Today

All of these patterns are fully implemented in SaaS Starter. Skip 2-6 weeks of setup.

F

Firas Sayah

Senior Software Engineer

Full-stack developer with 5+ years building production SaaS applications with NestJS and Angular.