TenancyJS
Adapters

TypeORM

Protected TypeORM repository isolation for PostgreSQL and MySQL.

tenancyjs-adapter-typeorm provides all three strategies on PostgreSQL plus adapter-enforced row-level and database-per-tenant on MySQL. This page shows it wired end to end with Express; for another framework, swap only the integration (see below).

Install

npm install tenancyjs-core tenancyjs-adapter-typeorm tenancyjs-integration-express typeorm

Wire it into your app

Create the manager + adapter

tenancy.ts
import { TenancyManager } from "tenancyjs-core";
import { createTypeOrmTenancy } from "tenancyjs-adapter-typeorm";
import { dataSource, Order } from "./data-source";

export interface Tenant {
  readonly id: string;
}

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

export const tenancy = createTypeOrmTenancy({
  manager,
  dataSource,
  tenantEntities: [
    {
      entity: Order,
      table: "app.orders",
      tenantProperty: "tenantId",
      tenantColumn: "tenant_id",
    },
  ],
});

await tenancy.validate();

Bind it to requests

tenancy.run gives you a narrow protected client-not a native EntityManager or repository.

server.ts
import express from "express";
import { createExpressTenancyMiddleware } from "tenancyjs-integration-express";
import { manager, tenancy } from "./tenancy";
import { Order } from "./data-source";

const app = express();

app.use(createExpressTenancyMiddleware({ manager, resolver }));

app.get("/orders", async (_req, res) => {
  const orders = await tenancy.run((client) =>
    client.repository(Order).findBy({ status: "open" }),
  );
  res.json(orders); // only the current tenant's rows
});

Other strategies

Schema mode requires unqualified entity metadata and table config:

createTypeOrmTenancy({
  manager,
  dataSource,
  strategy: "schemaPerTenant",
  schema: (tenant) => tenant.schemaName,
  tenantEntities: [{ entity: Order, table: "orders" }],
});

Database mode leases initialized tenant DataSource objects from the bounded cache:

createTypeOrmTenancy({
  manager,
  dataSource: landlordDataSource,
  strategy: "databasePerTenant",
  tenantEntities: [{ entity: Order, table: "orders" }],
  connection: (tenant) => ({
    key: tenant.databaseKey,
    create: () => createTenantDataSource(tenant.databaseSecretRef),
  }),
});

Call validate() before serving traffic and close() during shutdown. Native managers, query builders, relations, raw SQL, schema sync, and migrations remain outside the protected surface.

For MySQL, pass dialect: "mysql". Row isolation is experimental and adapter-enforced because MySQL has no RLS. MySQL schema and database are synonyms, so use databasePerTenant instead of schema mode.

Full query freedom: unrestricted()

In every other strategy the protected client rejects native managers, joins, and raw SQL - it's the only guard, so it can't let through what it can't prove is tenant-safe (see Limitations). Database-per-tenant is different: the leased connection is the tenant's own database, so any query is isolated by construction. There, the client gives you an escape hatch that returns the leased EntityManager - full TypeORM freedom:

const report = await tenancy.run(async (client) => {
  // The raw, tenant-scoped EntityManager - query builder, relations, raw SQL.
  const manager = client.unrestricted();

  return manager
    .createQueryBuilder(Order, "order")
    .innerJoin("order.customer", "customer")
    .where("order.total > :min", { min: 1000 })
    .select(["order.id", "customer.name"])
    .getRawMany();
});

unrestricted() is fail-closed. It returns the real EntityManager only in a genuinely database-enforced scope - a database-per-tenant config running in tenant mode, where a per-tenant connection was actually leased. It throws in row-level and schema-per-tenant scopes, and even in a database-per-tenant config used in central mode (which runs on the shared admin connection, not a tenant's database). The freedom comes from the connection boundary, never from the config name (ADR-0033).

Use a different framework

Keep the adapter half; only swap the integration import:

On this page