← All posts
Engineering12 min readApril 22, 2026

Inside our multi-tenant architecture — one DB, isolated tenants

How we run SmartRestro for 50+ restaurants on one MongoDB cluster without leaking a single order across tenants.

By Osama Khan

The trap: per-tenant databases

Lots of SaaS teams default to "one database per customer". It feels safe. It's actually a nightmare:

  • Migrations multiply (you have to run them N times)
  • Backups multiply
  • Connection pool exhaustion at scale
  • Onboarding a new customer takes minutes instead of milliseconds

We chose the harder-up-front, easier-forever path: **one MongoDB cluster, every document tagged with a tenant ID, server-side enforcement on every query.**

The pattern

Every collection has a `restaurant: ObjectId` field. Every query goes through a `branchScope(req)` helper:

```js function branchScope(req) { if (!req.user) return { _denyAll: true }; const role = req.user.role; const branchId = req.user.branchId; if (OWNERS.includes(role)) return {}; // owner sees all branches if (branchId) return { branch: String(branchId) }; return { _denyAll: true }; } ```

This gets spread into every Mongo query: `Order.find({ ...branchScope(req), restaurant: req.restaurant._id })`.

Why server-side, not client-side

Some teams enforce this in the frontend ("hide it in the UI"). That's not security — that's obscurity. A determined user opens DevTools and queries directly. Server-side is the only line that matters.

How we prevent forgetting to spread the scope

Every new feature reviewer's first question is "where's the branchScope?" Code review catches 95% of misses. The remaining 5% is caught by an integration test that creates two restaurants and verifies their data doesn't cross-pollute.

Backups + indexes

  • Daily full snapshots at MongoDB Atlas, retained 7 days
  • Indexes optimized for the most common access pattern: `{ restaurant: 1, branch: 1, createdAt: -1 }`
  • Hot path queries run < 50ms p95

What we'd do differently

At 1,000+ tenants, we'll likely shard by region (Pakistan, MENA, etc.). Right now at <100, one cluster is overkill — but we built it that way from day one because the migration to sharding is much easier than the migration from per-tenant databases.

The lesson

Pick the architecture that scales to 100× your current size, not 1,000×. Premature scaling is its own form of debt.

Enjoyed this?

More writing from inside our build.