Getting Started
Build a working multi-tenant API endpoint in 5 minutes.
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 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
|-- 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-standard idempotency with response 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/data-subject | GDPR/CCPA export and erase workflows |
| @nestarc/jobs | Tenant-aware background jobs with fair scheduling |
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.