Building a SaaS application requires careful architectural decisions. This guide covers patterns for multi-tenant systems that scale.
Updated guide: tenancy trade-off table, Postgres Row-Level Security pattern, and scaling checklist with where to get a build estimate. Reference: AWS SaaS Architecture Fundamentals — Tenancy Models (silo / pool / bridge).
1. Which tenancy model fits?
| Model | Isolation | Cost / ops overhead | When it fits |
|---|---|---|---|
| Database per tenant (silo) | Highest — blast radius contained per DB | Highest — migrations, backups, pools x N tenants | Regulated data, large enterprise tenants, per-tenant restore required |
| Schema per tenant (bridge) | Good — shared DB, separate schemas | Medium — simpler than N DBs, still N migrations | Mid-size B2B needing logical separation |
Shared DB, shared schema with tenant_id (pool) | Lowest — one missed filter can leak | Lowest — one migration, one pool | Most new SaaS: start here with enforcement + tests |
RLS note: In pool model, do not rely on remembering WHERE tenant_id = ?. Enforce it in Postgres with Row-Level Security set from trusted auth context.

Silo isolates per database, bridge per schema, pool per row: one migration and one pool, but isolation must be enforced and tested.
2. Minimal tenant-context pattern
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.tenant_id')::uuid);// Express/Next middleware — illustration
async function tenantContext(req, res, next) {
const tenantId = req.auth?.tenantId; // from verified JWT/session, never query param
if (!tenantId) return res.status(401).end();
await db.query(`SELECT set_config('app.tenant_id', $1, true)`, [tenantId]);
next();
}Test: user A cannot read user B row even with crafted ID; migration fails closed if app.tenant_id unset.
3. Per-tenant request checklist
- API Gateway: route by subdomain/header/JWT
tenantId; per-tenant rate limit keyed by tenant + plan; transform for versioning - Auth: verify JWT, load tenant membership + role, deny cross-tenant by default; audit tenant switch
- Service layer: pass
tenantIdexplicitly; scale hot tenants independently; flags per tenant - Data layer: pooled connections with
statement_timeout; RLS everywhere; drill restore: per-tenant for silo/bridge, point-in-time + logical export for pool
See related builds in our Portfolio before scoping.

Request flow: Gateway routes per tenant, Auth verifies membership, Service passes tenantId explicitly, Postgres RLS enforces tenant_id, Redis and queue branch per tenant.
4. Scaling without re-architecture
- Horizontal: stateless instances behind load balancer; avoid sticky sessions
- Reads: read replicas for read-heavy tenants; route analytics to replica
- Cache: Redis for session + hot queries, keys
t:{tenantId}:..., short TTL + invalidation on write - Async: queue for emails, reports, webhooks, image processing; per-tenant concurrency limits to avoid noisy neighbor
5. Start shared, isolate later
- Start pool + RLS if no regulatory silo requirement
- Move 1-2 large/noisy tenants to bridge/silo when metrics show need
- Keep path open:
tenant_idfrom day 1, tenant-aware backups, tenant-keyed limits. Explore scope in Services.
When you are ready to scope the move, Build my estimate.
Conclusion
Balance isolation, cost, and operability. Most teams should start shared + RLS, prove isolation with tests, then isolate only tenants that justify ops cost. If you need a clearer read, scope it with us.
Ready to scope? Build my estimate — or Send a brief. See all work.
