js

Build Multi-Tenant SaaS with NestJS, Prisma, PostgreSQL: Complete RLS Implementation Guide

Learn to build scalable multi-tenant SaaS apps with NestJS, Prisma & PostgreSQL RLS. Master tenant isolation, authentication & performance optimization.

Build Multi-Tenant SaaS with NestJS, Prisma, PostgreSQL: Complete RLS Implementation Guide

Building robust multi-tenant SaaS applications has become increasingly vital in my work with cloud-native systems. Just last month, a client’s data isolation requirements forced me to reevaluate traditional approaches, leading me to PostgreSQL’s Row-Level Security combined with NestJS and Prisma. This combination creates a scalable foundation that maintains strict tenant separation while simplifying infrastructure. Let me walk you through how this works.

Multi-tenancy means serving multiple customers from a single application instance. Why choose shared-database RLS over alternatives? When you have hundreds of tenants, managing separate databases becomes impractical. RLS provides data isolation at the database level while keeping operational complexity low. How does this actually prevent tenant data leaks? Let’s examine the architecture.

Start with a clean NestJS setup:

nest new saas-app --strict
npm install @prisma/client prisma
npx prisma init

Our Prisma schema defines tenant-aware models. Notice the tenantId field in every table:

model Tenant {
  id        String   @id @default(cuid())
  subdomain String   @unique
  users     User[]
}

model User {
  id        String @id @default(cuid())
  tenantId  String
  tenant    Tenant @relation(fields: [tenantId], references: [id])
}

The real magic happens in PostgreSQL. We enable Row-Level Security and create isolation policies:

ALTER TABLE "User" ENABLE ROW LEVEL SECURITY;

CREATE POLICY user_tenant_isolation ON "User"
FOR ALL USING (
  "tenantId" = current_setting('app.current_tenant')::text
);

This ensures every query automatically filters by tenant context. But how do we set that context securely?

Authentication ties users to tenants. We implement a JWT strategy containing tenant IDs:

// auth.strategy.ts
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET,
    });
  }

  validate(payload: { sub: string; tenantId: string }) {
    return { userId: payload.sub, tenantId: payload.tenantId };
  }
}

Before executing queries, we set the tenant context using Prisma middleware:

// prisma.service.ts
this.prisma.$use(async (params, next) => {
  const tenantId = getCurrentTenantId();
  
  if (params.model === 'User') {
    params.args.where = { ...params.args.where, tenantId };
  }

  return next(params);
});

For RLS to work, we must set the PostgreSQL session variable before each operation:

// tenant.decorator.ts
export const SetTenantContext = createParamDecorator(
  (_, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const tenantId = request.user?.tenantId;
    
    return this.prisma.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, false)`;
  }
);

Now consider performance. Without proper indexing, tenant queries become slow as data grows. Add composite indexes:

model Task {
  tenantId String
  projectId String

  @@index([tenantId, projectId])
}

For testing, we mock tenant contexts:

// test.setup.ts
beforeEach(() => {
  jest.spyOn(tenantService, 'getTenantId').mockReturnValue('test_tenant');
});

During deployment, remember these key steps:

  1. Enable connection pooling with PgBouncer
  2. Set pool_timeout to recycle connections
  3. Use SET ROLE for RLS privileges
  4. Enable SSL for all database connections

Common pitfalls? Forgetting to add tenantId in every table relation tops the list. Another is neglecting to test RLS policies with direct database access. Always verify isolation by switching tenant contexts manually.

The beauty of this approach shines when onboarding new tenants:

// tenant.service.ts
async createTenant(subdomain: string) {
  return this.prisma.$transaction([
    prisma.tenant.create({ data: { subdomain } }),
    prisma.$executeRaw`CREATE SCHEMA IF NOT EXISTS ${subdomain}`
  ]);
}

Notice we’re using schemas for supplementary data? This hybrid approach combines RLS efficiency with schema flexibility for tenant-specific customizations.

Building SaaS applications requires thoughtful tradeoffs. By leveraging PostgreSQL’s RLS, we achieve robust data isolation without sacrificing maintainability. Prisma’s middleware keeps our code clean, while NestJS provides the architectural backbone. What other patterns have you found effective for multi-tenancy?

I’ve shared the core techniques that solved real-world isolation challenges for my clients. If this approach resonates with your projects, let me know your thoughts in the comments. Found this useful? Share it with your team facing similar SaaS architecture decisions.

Keywords: multi-tenant SaaS NestJS, NestJS Prisma PostgreSQL tutorial, PostgreSQL row-level security RLS, multi-tenancy database design patterns, tenant isolation SaaS architecture, NestJS authentication authorization guide, Prisma multi-tenant configuration, scalable SaaS application development, PostgreSQL RLS implementation tutorial, tenant-aware API development



Similar Posts
Blog Image
Complete Next.js Prisma Integration Guide: Build Type-Safe Full-Stack Apps with Modern ORM

Learn to integrate Next.js with Prisma ORM for type-safe, full-stack web applications. Build database-driven apps with unified frontend and backend code.

Blog Image
Build High-Performance GraphQL API: Apollo Server, DataLoader & PostgreSQL Query Optimization Guide

Build high-performance GraphQL APIs with Apollo Server, DataLoader & PostgreSQL optimization. Learn N+1 solutions, query optimization, auth & production deployment.

Blog Image
Build Event-Driven Microservices: Complete Node.js, RabbitMQ, and MongoDB Implementation Guide

Learn to build scalable event-driven microservices with Node.js, RabbitMQ & MongoDB. Master CQRS, Saga patterns, and resilient distributed systems.

Blog Image
Build High-Performance GraphQL API with NestJS, Prisma & Redis: Complete Guide

Learn to build a high-performance GraphQL API with NestJS, Prisma ORM, and Redis caching. Master DataLoader, authentication, and optimization techniques.

Blog Image
Build Event-Driven Architecture: NestJS, Kafka & MongoDB Change Streams for Scalable Microservices

Learn to build scalable event-driven systems with NestJS, Kafka, and MongoDB Change Streams. Master microservices communication, event sourcing, and real-time data sync.

Blog Image
Complete Guide to Integrating Next.js with Prisma ORM for Type-Safe Full-Stack Development

Learn how to integrate Next.js with Prisma ORM for type-safe full-stack development. Build faster, SEO-friendly web apps with complete TypeScript support.