Core Engine
The foundation of your multi-tenant architecture. @node-tenant/tenant-core manages tenant lifecycles, global state, and context propagation through your Node.js application.
Overview
What is it? The @node-tenant/tenant-core package is the mandatory base dependency for all other modules in this ecosystem. It contains the central TenantManager.
Real-world Analogy
Think of your server as a busy hotel. tenant-core is the Front Desk Receptionist. When a guest (a web request) walks in, the Receptionist checks their ID, finds their room key (tenant context), and makes sure they only go to their assigned room. Without the Receptionist, guests would wander into each other's rooms!
It orchestrates how tenants are created, updated, and injected into the execution context using Node's native AsyncLocalStorage.
tenant-core. It acts as the central brain, connecting your HTTP frameworks (like tenant-express) to your databases (like tenant-storage-prisma).Installation
To get started, install the package using your favorite package manager. You will also need to install a storage adapter (e.g., Prisma or MongoDB) alongside it.
npm install @node-tenant/tenant-core
Detailed Setup Example
How do you set it up? The TenantManager must be initialized with at least one "storage adapter". This adapter tells the manager exactly how to save your list of customers (tenants) to your database. Below is a complete example of how to start the engine and run code safely isolated for a specific customer.
import { TenantManager, TenantContextManager } from '@node-tenant/tenant-core';
// We use Prisma in this example to save the tenant list
import { createPrismaStorage } from '@node-tenant/tenant-storage-prisma';
import { PrismaClient } from '@prisma/client';
const prismaClient = new PrismaClient();
const controlStorage = createPrismaStorage(prismaClient);
// 1. Initialize the central Tenant Manager (The Front Desk Receptionist)
const tenantManager = new TenantManager({
storage: controlStorage,
// (Optional) If you want all new customers to have these default settings:
defaultConfig: {
maxUsers: 100,
features: ['basic_analytics']
},
// Highly recommended: Logs exactly who changed what, for security audits.
auditEnabled: true
});
async function bootstrap() {
// 2. Create a new tenant (a new customer signing up for your app)
const acmeTenant = await tenantManager.createTenant({
name: 'Acme Corporation',
slug: 'acme-corp',
strategy: 'tenantId', // They will share a database with others
status: 'active'
});
console.log('Successfully provisioned tenant:', acmeTenant.id);
// 3. Manually running code "as" a specific customer.
// This is very useful if you have background tasks (like sending a weekly email report)
// that need to fetch data belonging only to Acme Corp.
await tenantManager.runAsTenant(acmeTenant.id, async () => {
// Everything executed inside this block is securely trapped in Acme's context!
const activeContext = TenantContextManager.currentOrThrow();
console.log('Current running context is:', activeContext.name);
// If you run database queries here using our ORM adapters,
// it will automatically only fetch Acme's data!
});
}
bootstrap().catch(console.error);
Line-by-Line Breakdown
- Line 10-18: We create the
TenantManager. This should only be done once globally in your app. - Line 22-27: We tell the manager to create a new customer in the database.
- Line 34:
runAsTenantcreates an invisible "sandbox" around the function block. Any code inside that block will automatically know it belongs to Acme.
Events and Hooks
What are events? TenantManager is an "EventEmitter". This means it can shout out loud when something important happens (e.g. "Hey everyone, a new tenant was just created!"). You can write code that "listens" for these shouts and reacts to them.
Why is this useful? It's perfect for connecting to third-party services. When a customer signs up, you can automatically tell Stripe to create a billing profile, or tell AWS to spin up a new server.
// Listen for the 'tenant.created' shout
tenantManager.on('tenant.created', async (tenant) => {
console.log(`Provisioning billing pipeline for ${tenant.name}...`);
// Example: Automatically charge the customer via Stripe
// await stripe.customers.create({ email: tenant.contactEmail });
});
// Listen for when a tenant's data changes
tenantManager.on('tenant.updated', async ({ previous, current }) => {
// If their account was just suspended...
if (previous.status === 'active' && current.status === 'suspended') {
console.warn(`Alert: Tenant ${current.name} was suspended. Stopping their services.`);
}
});
// Advanced: Listen for every time a web request enters a tenant's sandbox
tenantManager.on('tenant.switched', ({ from, to }) => {
console.debug(`Context shifted from ${from?.name || 'SYSTEM'} to ${to.name}`);
});