Fail-closed, TypeScript-first multi-tenancy for Node.js.
One tenant contract for every framework and ORM you already use - not a replacement for them. Tenant
identity rides the async execution scope, so your queries stay scoped without threading a tenantId
through every call. And the guarantee that matters: any tenant-aware access without a valid context
throws - it never returns another tenant's data.
await tenancy.runWithTenant(tenant, async () => { // Every registered adapter scopes data access to this tenant. // No tenant context => it throws. No silent fallback to unscoped data. });
Read the docs first — tenancyjs.pages.dev/docs. Multi-tenancy is a security boundary: skim the Rules & limitations and Resolving tenants before you wire anything. If you use an AI assistant, run
npx tenancyjs-cli init --ai-context— it writes aTENANCY.mdthat teaches the assistant the fail-closed rules for your stack.
0.2.0. Safe for real use; the API is still settling before1.0(semver0.x). Every "supported" cell below is proven by a two-tenant adversarial isolation test on a real database - nothing is marked supported on faith.
See it in action
One command scaffolds your stack. npx tenancyjs-cli init detects your framework and ORM, asks your
isolation strategy, and writes the wiring — plus an optional TENANCY.md that teaches your AI assistant
the fail-closed rules.
There's also a runnable two-tenant isolation demo (Express + Sequelize, database-per-tenant) in
demo/: colliding primary keys stay isolated, a forgotten tenant scope throws instead of
leaking, a spoofed x-tenant-id is refused with a sanitized 404, and a leak test proves it — all
against a real database.
Install
npm install tenancyjs-core
# then add an adapter + framework integration for your stack, e.g.
npm install tenancyjs-adapter-prisma tenancyjs-integration-expressThree isolation strategies, one contract
| Strategy | What it means | Adapters |
|---|---|---|
| Single database (row-level) | Shared tables, tenant_id + forced Postgres RLS or query-scoping |
Knex · Lucid · Prisma · TypeORM · Sequelize · Drizzle · Mongoose |
| Schema per tenant | One Postgres schema per tenant via search_path or a schema-bound Prisma driver client |
Knex · Lucid · Prisma · TypeORM · Sequelize · Drizzle |
| Database per tenant | A separate database per tenant, routed through a bounded connection cache | Knex · Lucid · Prisma · TypeORM · Sequelize · Drizzle · Mongoose |
MySQL has no separate schema namespace (SCHEMA is a synonym for DATABASE), so its equivalent is
database-per-tenant. MongoDB likewise supports database-per-tenant, not SQL schema-per-tenant.
Supported stacks
- Frameworks: Express 5, Next.js (App Router), AdonisJS 7, NestJS 11 (Express or Fastify) - plus a framework-neutral core.
- ORMs / databases: Prisma, TypeORM, Sequelize, and Drizzle (PostgreSQL + MySQL; MySQL row-level is experimental); Knex and Lucid 22 (PostgreSQL); Mongoose 9 (MongoDB replica set). Node.js 24+.
Operational CLI
tenancyjs-cli scaffolds and operates a tenancy without ever guessing about your stack. It loads your
own tenancy.config.ts at runtime (Node 24 strips the types - no transpiler dependency) and acts
against your live tenants:
npx tenancyjs-cli init # scaffold for your framework + ORM npx tenancyjs-cli tenant check # verify the runtime + warn on untested combos npx tenancyjs-cli tenant list # read your bring-your-own tenant store npx tenancyjs-cli tenant create acme --set plan=pro npx tenancyjs-cli tenant migrate --all # delegate to your migrator, per tenant npx tenancyjs-cli run ./backfill.ts --tenant acme
Registry, provisioning, and migrations all go through your store and hooks - the CLI orchestrates and fails closed, but never invents ORM behaviour it hasn't tested.
Security
Tenant identity is not authorization - your app still owns auth. TenancyJS guarantees that unknown, suspended, or ambiguous tenants never become central context, that a misbehaving tenant store cannot hand back the wrong tenant, that secrets are redacted from CLI output, and that cleanup always runs. See the security model.
Development
Requires Node.js 24+ and pnpm 10. Run the full gate with pnpm check (add TEST_DATABASE_URL /
MYSQL_TEST_DATABASE_URL / TEST_MONGODB_URL to run the real-database isolation tests). Runnable
examples live in a separate repo (see examples/README.md).
License
MIT - see LICENSE.
