Getting Started
Build a working multi-tenant API endpoint in 5 minutes.
Not sure which module to adopt first? Start with the package comparison or the Adoption Roadmap.
Prerequisites
| Tool | Version |
|---|---|
| Node.js | 18+ |
| NestJS | 10 or 11 |
| Prisma | 5 or 6 |
| PostgreSQL | 14+ |
Already have a NestJS + Prisma project?
Skip to Step 2.
Step 1: Install
npm install @nestarc/tenancyStep 2: Enable RLS
Add a tenant_id column and enable Row Level Security on your table:
ALTER TABLE users ADD COLUMN tenant_id TEXT NOT NULL;
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE users FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON users
USING (tenant_id = current_setting('app.current_tenant', true)::text);WARNING
Both ENABLE and FORCE are required. Without FORCE, the table owner role bypasses RLS entirely. See 5 Common Multi-Tenancy Pitfalls for details.
Step 3: Register the Module
// app.module.ts
import { Module } from '@nestjs/common';
import { TenancyModule } from '@nestarc/tenancy';
import { PrismaService } from './prisma.service';
import { UsersModule } from './users/users.module';
@Module({
imports: [
TenancyModule.forRoot({
tenantExtractor: 'X-Tenant-Id',
}),
UsersModule,
],
providers: [PrismaService],
exports: [PrismaService],
})
export class AppModule {}Step 4: Extend Prisma
// prisma.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { TenancyService, createPrismaTenancyExtension } from '@nestarc/tenancy';
@Injectable()
export class PrismaService implements OnModuleInit {
public readonly client;
constructor(private readonly tenancyService: TenancyService) {
const prisma = new PrismaClient();
this.client = prisma.$extends(
createPrismaTenancyExtension(tenancyService),
);
}
async onModuleInit() {
await this.client.$connect();
}
}Step 5: Create an API Endpoint
// users/users.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma.service';
@Injectable()
export class UsersService {
constructor(private readonly prisma: PrismaService) {}
findAll() {
// RLS automatically filters by tenant — no manual WHERE clause needed
return this.prisma.client.user.findMany();
}
create(name: string) {
// tenant_id is auto-injected by the Prisma extension
return this.prisma.client.user.create({ data: { name } });
}
}// users/users.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
@Post()
create(@Body('name') name: string) {
return this.usersService.create(name);
}
}Step 6: Test It
# Create a user as tenant-a
curl -X POST http://localhost:3000/users \
-H "X-Tenant-Id: tenant-a" \
-H "Content-Type: application/json" \
-d '{"name": "Alice"}'
# Create a user as tenant-b
curl -X POST http://localhost:3000/users \
-H "X-Tenant-Id: tenant-b" \
-H "Content-Type: application/json" \
-d '{"name": "Bob"}'
# Query as tenant-a — only sees Alice
curl http://localhost:3000/users -H "X-Tenant-Id: tenant-a"
# => [{"id": 1, "name": "Alice", "tenantId": "tenant-a"}]
# Query as tenant-b — only sees Bob
curl http://localhost:3000/users -H "X-Tenant-Id: tenant-b"
# => [{"id": 2, "name": "Bob", "tenantId": "tenant-b"}]That's it. PostgreSQL RLS enforces tenant isolation at the database level — no data leaks, no manual filtering.
What's Next?
5 min — Standardize your API responses
Add @nestarc/safe-response to auto-wrap all responses with consistent error codes, pagination metadata, and Swagger schemas. Quick Start →
10 min — Add audit logging
Track every create, update, and delete automatically — no code changes to your services. Quick Start →
30 min — Full tutorial
Build a complete multi-tenant task management API with tenancy + safe-response + testing. Full Tutorial →
See the package comparison and Adoption Roadmap for the recommended adoption path.
Stack Overview
All nestarc packages share a common foundation and compose via Prisma extensions:
Your NestJS App
|-- Request/API layer: safe-response, pagination, idempotency, api-keys
|-- Domain/data layer: tenancy, soft-delete, audit-log, feature-flag, rbac
|-- Events/workers: outbox, webhook, data-subject, jobs
`-- PostgreSQL + Prisma| Package | Role |
|---|---|
| @nestarc/tenancy | Row-level tenant isolation via PostgreSQL RLS |
| @nestarc/safe-response | Standardized API response wrapping + Swagger |
| @nestarc/audit-log | Automatic CUD change tracking |
| @nestarc/feature-flag | DB-based feature flags with tenant overrides |
| @nestarc/soft-delete | Prisma soft-delete with cascade and restore |
| @nestarc/pagination | Cursor + offset pagination with filters |
| @nestarc/idempotency | IETF draft-07-compatible idempotency with response/header replay |
| @nestarc/outbox | Transactional outbox for reliable domain events |
| @nestarc/webhook | Outbound webhook delivery with signing, retry, and logs |
| @nestarc/api-keys | Tenant-scoped API keys with scoped guards |
| @nestarc/rbac | Tenant-aware roles, permissions, and resource-scoped guards |
| @nestarc/data-subject | GDPR/CCPA export and erase workflows |
| @nestarc/jobs | Tenant-aware background jobs with fair scheduling |
Tooling
@nestarc/mcp-guard is published under the same npm scope, but is separate from the NestJS SaaS module stack. It statically scans MCP servers and client configuration files before you connect them to AI coding tools. See mcp-guard.
Prisma Extension Chaining
Multiple nestarc packages compose as Prisma extensions:
const prisma = new PrismaClient()
.$extends(createPrismaTenancyExtension(tenancyService))
.$extends(createPrismaSoftDeleteExtension({ softDeleteModels: ['User'] }))
.$extends(createAuditExtension({ trackedModels: ['User'] }));INFO
Extension order matters. See the Prisma Extension Chaining guide for details.