What Multi-Tenancy Actually Means
Multi-tenancy means your application serves multiple customers (tenants) from a single codebase and often a shared database β while keeping each customer's data completely isolated.
The alternative is single-tenancy: one deployment per customer. That doesn't scale β 100 customers means 100 servers to manage.
Three Approaches to Tenant Isolation
1. Shared database, shared schema (row-level isolation)
Every tenant's data in the same tables. A tenant_id column on every table determines ownership.
Pros: Simple, cheap, easy cross-tenant analytics. Cons: One access control bug exposes everyone's data.
This is the right starting point for 90% of early-stage SaaS.
2. Shared database, separate schemas
Each tenant gets their own schema. Tables look like tenant_abc.users.
Pros: Better isolation, easier single-tenant migration. Cons: More complex queries, per-tenant schema migrations.
Good for compliance-sensitive industries.
3. Separate database per tenant Each tenant gets their own database.
Pros: Complete isolation, easy deletion, compliance-friendly. Cons: Expensive, complex infrastructure, painful migrations.
Reserved for enterprise customers who specifically require it.
Implementing Row-Level Isolation
Start with option 1. Add tenant_id to every table:
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
name TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_projects_tenant ON projects(tenant_id);
Enforce tenant isolation in middleware:
export async function tenantMiddleware(req, res, next) {
const tenant = await getTenantFromRequest(req);
if (!tenant) return res.status(401).json({ error: 'Tenant not found' });
req.tenantId = tenant.id;
next();
}
In every database query, ALWAYS filter by tenant:
// Always do this:
const projects = await db.project.findMany({
where: { tenantId: req.tenantId }
});
// NEVER do this:
const projects = await db.project.findMany(); // exposes all tenants
Tenant Identification
Subdomains (acme.yourapp.com): Cleanest UX, requires wildcard SSL. Path-based (yourapp.com/acme/): Simplest to implement. Custom domains (app.acme.com): Premium feature for enterprise.
Start with subdomains if you can handle the SSL complexity β it creates better first impressions.
Auth in Multi-Tenant Systems
Your JWT should include tenantId:
const token = jwt.sign({
userId: user.id,
tenantId: user.tenantId,
role: user.role
}, process.env.JWT_SECRET);
For users belonging to multiple tenants (common in agency tools), store the active tenant in session and let users switch.
What to Build First, What to Skip
Build first: tenant identification middleware, row-level isolation on all tables, tenant-scoped API endpoints, basic usage tracking.
Skip for now: custom domain support, database-per-tenant, advanced tenant analytics. Add these when customers specifically ask.
The goal is correct and maintainable multi-tenancy β not perfect architecture that takes months to build.