@nestarc/tenancy
v0.16.2
Published
Multi-tenancy module for NestJS with PostgreSQL Row Level Security (RLS) and Prisma support
Maintainers
Readme
@nestarc/tenancy — NestJS multi-tenancy with PostgreSQL RLS and Prisma
A NestJS module that carries an authorized tenant ID through AsyncLocalStorage and sets PostgreSQL transaction-local context for Prisma model queries. PostgreSQL Row Level Security (RLS) policies then enforce which tenant's rows the database role may read or write.
Quick Start · Existing app setup · Usage reference · Compatibility · AI agent guide · Official docs
Requirements
| Dependency | 0.16.2 requirement |
| --- | --- |
| Node.js | ^22.23.4 \|\| ^24.22.1 |
| NestJS | 10 or 11 |
| Prisma | 6 or 7; the example uses Prisma 7 with @prisma/adapter-pg |
| Database | PostgreSQL with RLS enabled; use a supported, patched release |
Exact CI targets, optional peer combinations, pooler limits, and migrations are documented separately. Security fixes cover the latest published minor line; see SECURITY.md.
The Node.js minimum is a 0.16.2 security change.
Published 0.16.1 retains its original engine metadata, but applications using it should also
upgrade their Node runtime and HTTP parser dependencies. See the
runtime and parser security guidance.
Quick Start
Run the complete NestJS + Prisma example. It includes a Prisma schema, dedicated application database role, RLS SQL, two seeded tenants, authentication middleware, membership authorization, Prisma provider, and GET/POST controllers.
From a checkout of this repository, with Node.js and Docker available:
If you use nvm, run nvm install and nvm use after entering the checkout and
before npm ci; .nvmrc selects Node.js 24.22.1. Check node --version in the
shell that starts the application. Repository installs enforce the supported
engine range through .npmrc.
git clone https://github.com/nestarc/nestjs-tenancy.git
cd nestjs-tenancy
npm ci
npm run example:quickstart:setup
npm run example:quickstart:startIn another terminal:
curl http://localhost:3000/projects \
-H 'Authorization: Bearer demo-alice' \
-H 'X-Tenant-Id: 11111111-1111-4111-8111-111111111111'The response contains Alice's seeded project. Alice's token with Bob's tenant ID (22222222-2222-4222-8222-222222222222) returns 403. A missing or invalid demo credential returns 401. See the example for write requests, reset/cleanup commands, and expected responses.
A tenant header is a claim, not proof of access. The demo tokens are local fixtures. For production, replace them with your authentication provider and verify that the authenticated principal may access the resolved tenant. Register authentication with app.use(...) before Nest initialization so onTenantResolved receives the verified principal; importing an auth module first does not ensure that order. Authentication and adapter guide
Verify the example and documentation:
npm run test:docs
npm run test:docs:e2eThe first command checks documentation links, types, and HTTP contracts; the second verifies tenant isolation against the example's disposable PostgreSQL database. See the example verification notes for the exact checks and prerequisites.
Add to an existing app
Install the package and matching Prisma packages. This example uses Prisma 7; use the same supported exact version for the CLI, client, and adapter in your lockfile.
npm install @nestarc/tenancy @prisma/client@7 @prisma/adapter-pg@7 pg dotenv
npm install --save-dev prisma@7- Add a required tenant column to each tenant-scoped model. Generate and review database roles, grants, indexes, and RLS policies with
npx @nestarc/tenancy init. Use a non-owner application role withoutSUPERUSERorBYPASSRLSprivileges. - Register verified authentication before Nest initialization. Register
TenancyModule.forRoot({ tenantExtractor: 'X-Tenant-Id', onTenantResolved: ... }), with a hook that checks the principal's tenant membership. HTTP identifiers use UUID-like validation by default; slug identifiers require a validator. - Apply
createPrismaTenancyExtension(tenancyService)to the Prisma client and expose that extended client through your application provider. Keep the raw client private. See Prisma configuration. - Query through the extended client. For multiple operations in one interactive transaction, use
tenancyTransaction()and only its callback client. Write required tenant fields explicitly there. - Run
checkanddoctorwith the same setting key and runtime database role, then test tenant A/B and missing-context access.
How it works
Authenticated HTTP request + tenant claim
→ TenantMiddleware extracts and validates the tenant ID
→ AsyncLocalStorage establishes the tenant context
→ onTenantResolved authorizes it against the verified principal
→ TenancyGuard requires context for protected HTTP routes
→ Extended Prisma model query
→ transaction-local set_config() + query on the same transaction
→ PostgreSQL RLS evaluates the configured policiesAsyncLocalStorage avoids Nest's REQUEST-scoped providers. The extension adds transaction/context-setting work; see benchmark methodology and reproducible evidence for measuring it in your environment.
Usage reference
| Task | Guide |
| --- | --- |
| Header, subdomain, JWT, path, or fallback extraction; authentication; hooks | HTTP configuration |
| Model extension options, tenant injection, transactions, shared models, administration | Prisma and transactions |
| TenancyModule, TenancyService, decorators, errors | Core API reference |
| Testing helpers, events, OpenTelemetry, HTTP/RPC propagation, caching | Integrations |
| SQL policies, application role, schema migration, PgBouncer, CLI diagnostics | Database operations |
| Deploying the security update: runtime, HTTP parser, and resource keys | Upgrade and deployment sequence |
| Supported versions, exact CI lanes, deprecated APIs | Compatibility and migrations |
| Integrating the package with an AI coding agent | Version-scoped agent usage guide |
| Maintaining this repository and preparing a release | Documentation and release checklist |
Important boundaries
- Database configuration remains necessary. Apply RLS to every tenant table and connect with the intended application role. RLS protects rows; it does not authorize caller-supplied tenant IDs or isolate CPU/IO, caches, search indexes, or jobs.
- Fail-closed is enabled for model queries. Missing context throws
TenancyContextRequiredErrorunless explicitly bypassed at the client layer.sharedModelsandwithoutTenant()skip extension hooks; they do not disable database RLS or grant administrative access. Details - Raw SQL uses an explicit transaction.
$queryRawand$executeRaware outside the model extension. Run them on the callback client fromtenancyTransaction()so the tenant setting and SQL share one transaction. The package binds itsset_config()arguments; application SQL still needs proper parameterization. - Interactive transactions use the helper. The default extension's batch transaction does not automatically work inside a Prisma interactive transaction. The helper's raw transaction client does not auto-inject tenant fields. Transaction contract
- Non-HTTP transports need their own trust boundary. Kafka, Bull, and gRPC restoration need an explicit validator and producer-to-tenant authorization. WebSocket inbound enforcement is not provided. RPC guide
- Adapter and pooler support has conditions. Public structural request types do not prove every adapter hook works; review HTTP prerequisites. The pinned self-hosted PgBouncer transaction-mode matrix has a specific contract; managed poolers and Data Proxy require separate validation.
License
MIT
