Tenant Extractors
Five built-in extractors cover common multi-tenancy patterns:
Header (default)
TenancyModule.forRoot({
tenantExtractor: 'X-Tenant-Id', // shorthand for HeaderTenantExtractor
})Subdomain
import { SubdomainTenantExtractor } from '@nestarc/tenancy';
TenancyModule.forRoot({
tenantExtractor: new SubdomainTenantExtractor({
excludeSubdomains: ['www', 'api'], // optional, defaults to ['www']
}),
validateTenantId: (id) => /^[a-z0-9-]+$/.test(id),
})
// tenant1.app.com → 'tenant1'Note: Uses the
pslpackage for accurate ccTLD parsing (installed automatically as a dependency).
JWT Claim
import { JwtClaimTenantExtractor } from '@nestarc/tenancy';
TenancyModule.forRoot({
tenantExtractor: new JwtClaimTenantExtractor({
claimKey: 'org_id', // JWT payload key
headerName: 'authorization', // optional, defaults to 'authorization'
}),
})
// Authorization: Bearer eyJ... → payload.org_idSecurity: This extractor does not verify the JWT signature. You must ensure JWT signature verification happens at the middleware level — not in a NestJS Guard.
NestJS execution order is: Middleware → Guards → Interceptors → Pipes. Since
TenantMiddlewareruns at the middleware stage, a NestJS Guard (e.g.,@nestjs/passportAuthGuard) runs after the tenant is already resolved and cannot protect it.Middleware ordering:
TenancyModuleregistersTenantMiddlewareglobally via its ownconfigure()call. To run JWT verification before tenant extraction, you have two options:Option 1 (recommended) — Import an auth module before TenancyModule:
NestJS applies middleware in the order modules are initialized. If your auth middleware is registered in a module that is imported before
TenancyModule, it will run first.typescript// auth.module.ts — registers JWT verification middleware globally @Module({}) export class AuthModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(JwtVerifyMiddleware) // verifies signature, populates req.user .forRoutes('*'); } } // app.module.ts — import AuthModule BEFORE TenancyModule @Module({ imports: [ AuthModule, // middleware runs first TenancyModule.forRoot({ tenantExtractor: new JwtClaimTenantExtractor({ claimKey: 'org_id' }), }), ], }) export class AppModule {}Option 2 — Verify the JWT claim in
onTenantResolved:If you need to ensure the resolved tenant matches the authenticated user, use the
onTenantResolvedhook. This does not replace signature verification but lets you add an authorization check after extraction:typescriptTenancyModule.forRoot({ tenantExtractor: new JwtClaimTenantExtractor({ claimKey: 'org_id' }), onTenantResolved: (tenantId, req) => { // req.user is populated by an upstream auth middleware if (req.user?.org_id !== tenantId) { throw new ForbiddenException('Tenant mismatch'); } }, })
Path Parameter
import { PathTenantExtractor } from '@nestarc/tenancy';
TenancyModule.forRoot({
tenantExtractor: new PathTenantExtractor({
pattern: '/api/tenants/:tenantId/resources',
paramName: 'tenantId',
}),
})
// /api/tenants/acme/resources → 'acme'Composite (Fallback Chain)
import {
CompositeTenantExtractor,
HeaderTenantExtractor,
SubdomainTenantExtractor,
JwtClaimTenantExtractor,
} from '@nestarc/tenancy';
TenancyModule.forRoot({
tenantExtractor: new CompositeTenantExtractor([
new HeaderTenantExtractor('X-Tenant-Id'),
new SubdomainTenantExtractor(),
new JwtClaimTenantExtractor({ claimKey: 'org_id' }),
]),
})
// Tries each extractor in order, returns the first non-null resultCustom Extractor
import { TenantExtractor } from '@nestarc/tenancy';
import { Request } from 'express';
export class CookieTenantExtractor implements TenantExtractor {
extract(request: Request): string | null {
return request.cookies?.['tenant_id'] ?? null;
}
}