Multi-Tenancy
Single-tenant, shared multi-tenant, or fully isolated — Nest Auth supports all three.
Multi-tenancy is built into the core, not bolted on. The same APIs work whether you're single-tenant, supporting many tenants in one database, or running each tenant in its own database. You flip a flag rather than re-architect.
The three modes
tenant.enabled | tenant.mode | Storage | Best for |
|---|---|---|---|
false (default) | — | Single database, no tenantId column | Solo apps, internal tools, MVPs |
true | SHARED | Single database, every row carries tenantId | SaaS where tenants are cheap to spin up |
true | ISOLATED | Per-tenant database (or schema) | Enterprise / compliance-heavy customers |
SHARED mode
All tenants live in the same database. Every tenant-scoped table — nest_auth_user_accesses, your Order, your Invoice — carries a tenantId column. Every query the library issues filters by tenant. Every query you write should filter by tenant too.
Pros
- One database, one connection pool, simple ops.
- Cross-tenant queries trivially possible (super-admin views, support reports).
- Cheap to spin up a new tenant — one
INSERTintonest_auth_tenants.
Cons
- A bug in your tenant-filter query is a data-leak vulnerability. Use the request context consistently and write a repository wrapper that filters automatically if you can.
- Noisy-neighbour: a heavy tenant slows queries for everyone.
- Hard to satisfy compliance regimes that require physical isolation.
Where to filter by tenantId
The tenantId decorator reads from the AsyncLocalStorage-backed request context. The library populates it automatically based on the active session's tenant.
ISOLATED mode
Each tenant has its own database (or schema). The library still tracks tenants in a "control plane" database, but tenant data lives elsewhere.
Pros
- Hard isolation — a query in Tenant A cannot read Tenant B because it's literally a different database.
- Per-tenant restore: trivial.
- Per-tenant performance tuning, scaling, and per-region deployment all become possible.
Cons
- More expensive: one connection pool per tenant.
- Spin-up cost: provisioning a new database is slower than an
INSERT. - Cross-tenant queries (super-admin "all tenants" views) require fan-out.
In ISOLATED mode the library wires you to the right per-tenant data source automatically based on request.tenantId. Your own services just inject Repository<...> and the right connection is selected behind the scenes.
Tenant context — how it gets resolved
Every authenticated request carries an active tenant. The library resolves it in this order:
- JWT claim — the access token carries
tenantId(set when the session was created). switchTenantaction — explicit tenant switch viaPOST /auth/switch-tenantrotates the session.- Default tenant fallback — if
defaultTenantOptionsis configured and the user has no other tenant, the library puts them in the default.
The resolved value lands on the request context and is what every @CurrentTenantId()-style decorator reads.
Default tenant
For apps that are "really single-tenant but might add tenants later":
The library auto-creates this tenant on first boot and assigns new signups to it (in registrationHooks.onSignup). You can flip tenant.enabled to false later if you decide tenancy was overkill.
Creating tenants programmatically
Use TenantService:
The TenantCreatedEvent fires automatically — wire your billing system, Slack notification, default-data seeder, etc. to the event listener:
Switching tenants
A user can belong to multiple tenants — the nest_auth_user_accesses table stores the membership. Switching is one call:
The server issues a fresh session bound to the new tenant; the client persists the new tenantId; React's useUser() re-renders with the new context. The tenant-switcher recipe shows a complete dropdown UI.
Roles per tenant
A user's roles are tenant-scoped by default. Alice can be admin in tenant A and member in tenant B. The NestAuthUserAccess row holds the per-tenant role assignment.
For roles that span all tenants — global admins, support staff — use Platform Access instead.
Multi-platform / guard pattern
A common production pattern is one backend serving several frontends — an admin console, a tenant-facing portal, a mobile app. Each frontend should only allow login from users with a role on its guard, even if the same backend serves them all.
This is documented in detail in the multi-platform login recipe.
Where tenancy lives in the schema
| Table | Purpose |
|---|---|
nest_auth_tenants | One row per tenant (name, slug, isActive, metadata) |
nest_auth_user_accesses | Many-to-many between users and tenants, with per-tenant roles |
nest_auth_platform_accesses | Cross-tenant roles for staff (no tenant column) |
Your tenant-scoped tables (orders, invoices, …) | Carry a tenantId column in SHARED mode; live in a per-tenant DB in ISOLATED mode |
Decorators
| Decorator | Returns |
|---|---|
@CurrentTenantId() | The active tenantId (string) |
@CurrentTenant() | The full NestAuthTenant entity |
@CurrentUserAccess() | The current user's NestAuthUserAccess for this tenant |
@CurrentMembership() | Alias for @CurrentUserAccess() |
Related
- User Access & Platform Access — per-tenant vs cross-tenant roles.
- RBAC — roles, permissions, multiple guards.
- Tenant switcher recipe.
- Multi-platform login recipe.
TenantServicereference.