js

Build Multi-Tenant SaaS with NestJS, Prisma, and PostgreSQL Row-Level Security

Learn to build secure multi-tenant SaaS apps with NestJS, Prisma & PostgreSQL RLS. Complete guide with tenant isolation, auth, and best practices. Start building today!

Build Multi-Tenant SaaS with NestJS, Prisma, and PostgreSQL Row-Level Security

As I built my latest SaaS product, a critical question emerged: how can I securely serve multiple customers from a single application without compromising their data? That’s when I decided to implement multi-tenancy using NestJS, Prisma, and PostgreSQL’s Row-Level Security. Let me share this journey with you.

First, what exactly is multi-tenancy? It’s an architecture where one application instance serves multiple customers (tenants), with strict data separation between them. Why choose this approach? It reduces costs, simplifies maintenance, and scales efficiently. But how do we ensure ironclad data isolation? That’s where PostgreSQL RLS comes in.

Let’s set up our project. Start with these commands:

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

Now, imagine our database schema. We need tenants, users, and organizations. Here’s a simplified Prisma schema:

model Tenant {
  id   String @id @default(cuid())
  name String
  slug String @unique
}

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

The real magic happens with PostgreSQL Row-Level Security. This feature restricts database access at the row level. Try this SQL policy:

CREATE POLICY tenant_isolation_policy ON users
FOR ALL USING (
  tenant_id = current_setting('app.current_tenant_id')
);

See how it uses the tenant context? That’s our security backbone. Now, what happens when a user makes a request? We need to identify their tenant first.

In NestJS, we resolve tenants using middleware. Here’s a simplified version:

// tenant.middleware.ts
import { Request, Response, NextFunction } from 'express';

export function tenantMiddleware(req: Request, res: Response, next: NextFunction) {
  const tenantId = req.headers['x-tenant-id'] || req.hostname;
  req.tenantId = tenantId;
  next();
}

But how do we enforce this tenant context in database queries? We extend Prisma’s client:

// prisma.service.ts
import { PrismaClient } from '@prisma/client';
import { Injectable, OnModuleInit } from '@nestjs/common';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }

  async withTenant(tenantId: string) {
    return this.$extends({
      query: {
        async $allOperations({ args, query }) {
          const [, result] = await this.$transaction([
            this.$executeRaw`SELECT set_config('app.current_tenant_id', ${tenantId}, TRUE)`,
            query(args)
          ]);
          return result;
        }
      }
    });
  }
}

Notice how we set the tenant ID before each query? This activates our RLS policies automatically. What about authentication? We use JWT tokens containing the tenant ID.

When onboarding new tenants, our process must be seamless. Consider this user signup flow:

// auth.service.ts
async signUp(tenantSlug: string, signUpDto: SignUpDto) {
  const tenant = await this.prisma.tenant.findUnique({ 
    where: { slug: tenantSlug }
  });
  
  const hashedPassword = await bcrypt.hash(signUpDto.password, 10);
  
  return this.prisma.user.create({
    data: {
      email: signUpDto.email,
      password: hashedPassword,
      tenantId: tenant.id
    }
  });
}

Testing is crucial. How do we verify tenant isolation? I use integration tests that:

  1. Create two test tenants
  2. Add data to both
  3. Verify neither can access the other’s data

Performance considerations? Always:

  • Index tenant_id columns
  • Monitor connection pooling
  • Cache tenant-specific data carefully
  • Rate limit per tenant

Common pitfalls I’ve encountered:

  • Forgetting to set tenant context in background jobs
  • Caching data without tenant segregation
  • Not testing RLS policies thoroughly
  • Missing indexes on tenant_id columns

What if you need stricter isolation? For enterprise customers, consider separate databases. But for most SaaS applications, RLS provides excellent security with simpler operations. I’ve found this combination of NestJS, Prisma, and PostgreSQL RLS delivers robust multi-tenancy without excessive complexity.

Building this architecture taught me valuable lessons about scalable security. Have you tried implementing RLS before? What challenges did you face? If you found this helpful, share it with your network! Let me know your thoughts in the comments - I’d love to hear about your SaaS architecture experiences.

Keywords: multi-tenant SaaS, NestJS Prisma PostgreSQL, row-level security RLS, multi-tenancy architecture, tenant isolation database, SaaS application development, PostgreSQL tenant separation, NestJS authentication middleware, Prisma multi-tenant setup, scalable SaaS backend



Similar Posts
Blog Image
Complete Guide to Integrating Next.js with Prisma ORM: Build Type-Safe Full-Stack Applications

Learn how to integrate Next.js with Prisma ORM for type-safe, scalable web applications. Build powerful full-stack apps with seamless database operations today!

Blog Image
Build High-Performance Event-Driven Microservices with NestJS, RabbitMQ and Redis Tutorial

Learn to build scalable event-driven microservices with NestJS, RabbitMQ & Redis. Complete guide with TypeScript, caching, testing & deployment.

Blog Image
Complete Guide to Integrating Next.js with Prisma ORM: Type-Safe Database Operations Made Simple

Learn how to integrate Next.js with Prisma ORM for type-safe full-stack development. Build modern web apps with seamless database operations and migrations.

Blog Image
Building High-Performance GraphQL APIs: NestJS, Prisma, and Redis Caching Complete Guide

Learn to build scalable GraphQL APIs with NestJS, Prisma ORM, and Redis caching. Master DataLoader optimization, real-time subscriptions, and production-ready performance techniques.

Blog Image
Building Type-Safe Event-Driven Architecture with TypeScript NestJS and RabbitMQ Complete Guide

Learn to build scalable event-driven microservices with TypeScript, NestJS & RabbitMQ. Master type-safe event handling, message brokers & resilient architecture patterns.

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

Learn how to integrate Next.js with Prisma ORM for powerful full-stack TypeScript applications. Build type-safe, scalable web apps with seamless database operations.