System Design
10 min read2026-06-28

Building Scalable Multi-Tenant SaaS Backends with Node.js, TypeScript & PostgreSQL

Exploring schema-per-tenant vs row-level security (RLS) isolation strategies for mission-critical enterprise applications.

MS

Md Shahriar Shatab

IT Consultant & Lead Software Engineer

Multi-tenancy is at the heart of modern B2B SaaS platforms. Ensuring strict data isolation between corporate clients while maintaining developer velocity and low operational cost is one of the most critical design decisions an architect will make.

1. Isolation Models: Row-Level Security vs Schema per Tenant

When evaluating multi-tenant database designs, three major patterns emerge: Shared Database / Shared Schema (with tenant_id), Shared Database / Separate Schema, and Separate Database per Tenant.

  • Shared Schema + RLS: High resource efficiency, lower maintenance cost, supported natively by PostgreSQL.
  • Schema per Tenant: Harder data boundary separation, easier tenant backup & data deletion compliance.
  • Database per Tenant: Maximum isolation, suitable for strict compliance enterprise tiers (HIPAA/SOC2), higher cloud cost.

2. Implementing Row-Level Security in TypeScript

PostgreSQL Row-Level Security (RLS) lets you enforce access controls directly at the database engine layer, preventing accidental data leaks from application bug oversights.

schema.sqlsql
-- Enable RLS on organization data table
ALTER TABLE workspaces ENABLE ROW LEVEL SECURITY;

-- Create policy enforcing tenant scope
CREATE POLICY tenant_isolation_policy ON workspaces
    FOR ALL
    USING (tenant_id = current_setting('app.current_tenant_id'));

3. Managing Dynamic Connection Pools in Node.js

When serving thousands of active tenants, initializing separate connection pools per schema exhausts PostgreSQL memory. A dynamic pool manager wraps connection requests with session variables.

db-context.tstypescript
import { Pool, PoolClient } from 'pg';

export async function executeTenantQuery<T>(
  tenantId: string,
  queryText: string,
  params: any[] = []
): Promise<T[]> {
  const client: PoolClient = await pool.connect();
  try {
    await client.query("SELECT set_config('app.current_tenant_id', $1, true)", [tenantId]);
    const res = await client.query(queryText, params);
    return res.rows;
  } finally {
    client.release();
  }
}

Summary

For most SaaS applications, PostgreSQL RLS coupled with TypeScript middleware provides the optimal balance between strict isolation and cost-efficient cloud resource consumption.

Tags:Node.jsTypeScriptPostgreSQLSystem DesignSaaSSecurity