Skip to content

Getting Started

Build a working multi-tenant API endpoint in 5 minutes.

Prerequisites

ToolVersion
Node.js18+
NestJS10 or 11
Prisma5 or 6
PostgreSQL14+

Already have a NestJS + Prisma project?

Skip to Step 2.

Step 1: Install

bash
npm install @nestarc/tenancy

Step 2: Enable RLS

Add a tenant_id column and enable Row Level Security on your table:

sql
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

typescript
// 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

typescript
// 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

typescript
// 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 } });
  }
}
typescript
// 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

bash
# 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
PackageRole
@nestarc/tenancyRow-level tenant isolation via PostgreSQL RLS
@nestarc/safe-responseStandardized API response wrapping + Swagger
@nestarc/audit-logAutomatic CUD change tracking
@nestarc/feature-flagDB-based feature flags with tenant overrides
@nestarc/soft-deletePrisma soft-delete with cascade and restore
@nestarc/paginationCursor + offset pagination with filters
@nestarc/idempotencyIETF-standard idempotency with response replay
@nestarc/outboxTransactional outbox for reliable domain events
@nestarc/webhookOutbound webhook delivery with signing, retry, and logs
@nestarc/api-keysTenant-scoped API keys with scoped guards
@nestarc/data-subjectGDPR/CCPA export and erase workflows
@nestarc/jobsTenant-aware background jobs with fair scheduling

Prisma Extension Chaining

Multiple nestarc packages compose as Prisma extensions:

typescript
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.

Released under the MIT License.