Quickstart
Wire tenant context into an Express + Prisma app and run your first scoped query.
This walks through the smallest useful setup: a TenancyManager, a Prisma adapter, and Express
middleware that resolves the tenant per request. Using a different stack? Swap the adapter and
integration imports - the shape is identical (see Adapters and
Integrations).
Prefer not to hand-write this? npx tenancy init scaffolds all of it for Express + Prisma, AdonisJS +
Lucid, or Next.js + Prisma. See Installation.
Create a manager
The TenancyManager owns tenant context. It uses AsyncLocalStorage, so context flows through your
async calls automatically - no tenantId threading.
import { TenancyManager } from "tenancyjs-core";
export interface Tenant {
readonly id: string;
}
export const manager = new TenancyManager<Tenant>();Attach an adapter
An adapter enforces isolation inside your ORM. For Prisma, register the tenant-scoped models and apply
the adapter's extension to your client - the extended db is what you query with. (Using a
different ORM? See its adapter guide; the shape is the same.)
import { createPrismaAdapter } from "tenancyjs-adapter-prisma";
import { PrismaClient } from "@prisma/client";
const adapter = createPrismaAdapter({
manager,
tenantModels: { Post: {}, Order: {} },
});
// `db` is your tenant-scoped Prisma client.
export const db = new PrismaClient().$extends(adapter.extension);Run inside a tenant scope
Everything you do through db inside runWithTenant is scoped to that tenant. Outside a scope, it
fails closed - a loud error, never unscoped data.
await manager.runWithTenant({ id: "acme" }, async () => {
const orders = await db.order.findMany(); // only acme's orders
});
// Outside a scope:
await db.order.findMany();
// ✗ TenantContextError - refuses to run unscopedBind it to requests
The framework integration resolves the tenant from each request and opens the scope for its lifetime, so your route handlers are automatically scoped.
import express from "express";
import { createExpressTenancyMiddleware } from "tenancyjs-integration-express";
import { manager, db } from "./tenancy";
const app = express();
app.use(
createExpressTenancyMiddleware({
manager,
resolver: (req) => ({ id: req.subdomains.at(-1) ?? "" }),
}),
);
app.get("/orders", async (_req, res) => {
res.json(await db.order.findMany()); // already scoped
});If the resolver can't produce a valid tenant, the middleware responds 400/404 and your handler never runs - no unscoped request reaches your code.