TenancyJS
Integrations

Express

Add multi-tenancy to Express with one middleware - works with any ORM.

tenancyjs-integration-express resolves the tenant for each request and runs that request inside the tenant's scope. Everything downstream - routes, services, ORM queries - is automatically scoped.

Works with any ORM. Prisma · Knex · TypeORM · Sequelize · Drizzle · Mongoose. This guide uses Prisma; swap the adapter for yours (the middleware is identical).

Install

The framework integration plus your ORM's adapter (Prisma shown):

npm install tenancyjs-core tenancyjs-integration-express tenancyjs-adapter-prisma @prisma/client

Wire it up

Manager + adapter

tenancy.ts
import { TenancyManager } from "tenancyjs-core";
import { createPrismaAdapter } from "tenancyjs-adapter-prisma";
import { PrismaClient } from "@prisma/client";

export interface Tenant {
  readonly id: string;
}

export const manager = new TenancyManager<Tenant>();

const adapter = createPrismaAdapter({ manager, tenantModels: { Order: {} } });
export const db = new PrismaClient().$extends(adapter.extension);

The middleware

Register it early, before your routes. Give it your manager and a resolver that turns a request into a tenant.

server.ts
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 to the tenant
});

How it behaves

  • On success: the tenant scope is open for the whole request; adapters are scoped, and the scope is torn down when the response finishes.
  • On a resolution failure: a missing/invalid identifier or an unknown/suspended tenant makes the middleware respond 400/404 - your handler never runs.

Use a different ORM

Keep the middleware; swap the adapter half. Each adapter page has a complete Express example:

Resolving the tenant

The resolver is yours - subdomain, header, path, JWT claim. For lookups against your store and consistent 400/404 semantics, see Resolving tenants.

On this page