Multi-tenant SaaS API
Part of the Backend Developer Roadmap
In short
Every B2B SaaS, from Zoho to Freshworks, serves many companies from one codebase. The one bug that must never happen: Company A seeing Company B's data.
A backend serving multiple isolated customers (tenants) from one codebase and infrastructure — practice for data isolation strategies and tenant-aware authorization.

Think of it like this
One apartment building, many flats
Everyone shares the lift, the water tank and the watchman. But each flat has its own lock, and the watchman never hands flat 302's key to someone from 405. Tenants share the infrastructure, never each other's stuff.
Watch it happen
Every request, tenant-aware
1/5Work out the tenant from the subdomain, like acme.myapp.com, or from the token's org claim.
The big ideas
What you need to know
Choose an isolation model
Shared tables with tenant_id, a schema per tenant, or a database per tenant. More isolation costs more to run.
Enforce it in one place
Middleware or Postgres row-level security, not a WHERE clause each developer must remember.
Roles per tenant
The same person can be admin in one company and viewer in another. Store roles on the membership.
Fair usage
Rate limits and quotas per tenant, so one big customer can't slow everyone else down.
See it in code
Walk through the code
1ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;2 3CREATE POLICY tenant_isolation ON invoices4 USING (tenant_id = current_setting('app.tenant_id')::uuid);5 6-- In each request's transaction, the API sets the tenant first:7BEGIN;8SET LOCAL app.tenant_id = '6f1c2a9e-0b7d-4c1a-9f4e-2d8b3c5a7e10';9SELECT * FROM invoices; -- only this tenant's rows come back10COMMIT;1/3Turn on row-level security and add a policy: rows are visible only if tenant_id matches.
From app code, use SELECT set_config('app.tenant_id', $1, true) so the value is a safe query parameter. It lasts only for that transaction.
Save yourself the pain
Common mistakes
Oops
Relying on every developer to add WHERE tenant_id = ? to every query.
Do this instead
Someone will forget once. Enforce it centrally with RLS or a scoped repository layer, and test cross-tenant access.
Oops
Connecting as the table owner and assuming RLS still applies.
Do this instead
Table owners bypass RLS unless you add FORCE ROW LEVEL SECURITY. Use a separate, non-owner app role.
Your mission
Milestone 1: prove isolation
Quick check
What's the main advantage of enforcing tenant isolation with Postgres row-level security?
Go deeper
- 1Choose and implement a tenant isolation strategy — shared DB with tenant ID, or separate schemas.
- 2Ensure every query is scoped to the authenticated user's tenant — no data leaks across tenants.
- 3Add tenant-level configuration, e.g. custom settings per customer.
More projects at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.