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 Next.js Prisma Integration: Build Type-Safe Full-Stack Apps in 2024

Learn how to integrate Next.js with Prisma ORM for type-safe full-stack development. Build powerful React apps with seamless database connectivity and auto-generated APIs.

Blog Image
NestJS Microservice Tutorial: Event-Driven Architecture with RabbitMQ and MongoDB for Production

Learn to build production-ready event-driven microservices with NestJS, RabbitMQ & MongoDB. Complete guide covering event sourcing, error handling & deployment.

Blog Image
Production-Ready GraphQL Gateway: Build Federated Microservices with Apollo Federation and NestJS

Learn to build scalable GraphQL microservices with Apollo Federation, NestJS, authentication, caching, and production deployment strategies.

Blog Image
Complete Guide to Next.js Prisma Integration: Build Type-Safe Database-Driven Apps in 2024

Learn how to integrate Next.js with Prisma ORM for type-safe, database-driven web apps. Build powerful full-stack applications with seamless frontend-backend unity.

Blog Image
Complete Guide to Integrating Next.js with Prisma ORM for Full-Stack TypeScript Applications

Learn how to integrate Next.js with Prisma ORM for powerful full-stack web applications. Build type-safe database operations with seamless frontend-backend integration.

Blog Image
Build Type-Safe Event-Driven Microservices: NestJS, RabbitMQ, and Prisma Complete Tutorial

Learn to build scalable event-driven microservices with NestJS, RabbitMQ & Prisma. Complete guide with type-safe schemas, error handling & Docker deployment.