Devprimo

Choosing the right backend for a new SaaS build

7 min readUpdated SaaSArchitectureMulti-tenancy

Most SaaS architecture advice optimises for scale you don't have yet. The decisions worth agonising over are the small number that are genuinely hard to reverse — and tenancy is at the top of that list.

Reversible and irreversible decisions

Almost everything in a backend can be changed later at moderate cost. You can swap a queue, move a service, change hosting, even change language for one component. These are reversible, and treating them as sacred slows you down for no return.

A short list is not reversible without real pain: how tenancy is modelled, how identity works, and where the source of truth for money lives. We spend our design time there and move fast on everything else.

The practical test for whether something belongs on that list is to ask what changing it would touch. Swapping a queue touches the code that publishes and consumes. Changing how tenancy works touches every query, every migration you've already shipped, and every assumption in the codebase about who can see what. That difference in blast radius is the whole distinction, and it's worth applying deliberately rather than treating every decision as equally weighty.

Decide tenancy before you write anything

Shared database with a tenant column, schema per tenant, or database per tenant — this decision reaches into every query you will ever write. Retrofitting it means touching the entire data layer plus every migration you've shipped.

The three models, and who each is actually for

A shared database with a tenant column on every table is the cheapest to run and the simplest to migrate: one schema, one set of migrations, one connection pool. It's the right answer for most B2B SaaS, and it stays the right answer far longer than people expect.

Schema per tenant buys you clearer separation and per-tenant backup and restore, at the cost of migrations that now run N times and get slower with every customer you win. It's a reasonable middle ground for a few hundred large accounts, and a bad one for ten thousand small ones.

Database per tenant is what genuine isolation requirements look like — regulated data, contractual guarantees, per-customer residency. It's the most expensive to operate by a wide margin, so it should be driven by a requirement someone can point at in writing, not by a feeling about security.

Enforce scoping where it can't be forgotten

The trap isn't picking the shared model. The trap is enforcing it by convention, so that one forgotten WHERE clause leaks one customer's data into another's screen. Code review does not reliably catch this, because the query looks correct in isolation — the missing part is invisible.

So put the check somewhere it cannot be skipped: row-level security in the database, or a data-access layer that will not build a query without a tenant in scope. Both are more work in week one and far less work in year two. Then write the test that tries to read another tenant's row and asserts that it fails, because that is the one bug you cannot afford to find in production.

Identity is the other one-way door

Tenancy gets the attention; identity quietly causes as much retrofit pain and gets designed on the last afternoon before launch.

The decision that matters is not which auth library to use — that's swappable. It's whether a user belongs to exactly one account or can belong to several. Single-account is simpler and models most early products correctly. Multi-account is what agencies, consultancies and anyone with a parent company will eventually need, and adding it later means changing the shape of every permission check and every session in the system.

Related, and just as sticky: roles. A boolean admin flag is fine until the first customer asks for a read-only seat for their accountant, at which point you're rewriting authorisation across every endpoint. Modelling roles as data from the start costs very little and removes an entire category of future rework.

For the mechanics — password storage, session handling, MFA, SSO — use a provider or a well-maintained library. Authentication is a solved problem with a very unforgiving failure mode, and it is not where a new product earns anything.

Model billing early, build it late

You don't need billing in your first release. You do need a data model that can answer "what is this account entitled to?" and "what have they used?", because bolting those onto a schema that assumed neither is genuinely painful.

Then integrate a provider rather than building an engine. Billing looks simple until you meet proration, tax, dunning, and refunds against a changed plan. That's a product in itself, and it shouldn't be yours.

One rule that saves a lot of grief: keep entitlements in your database, not in the billing provider's. What a customer is allowed to do is a question your application must answer on every request, and it should not depend on a third-party API being reachable. Let the provider be the source of truth for money, and let your system be the source of truth for access, with the webhook that connects them treated as something that will occasionally arrive late, twice, or out of order.

Start with a monolith you could split

Microservices solve an organisational problem — many teams needing to deploy independently. If you have one team, they add distributed-systems failure modes and buy you nothing.

Build a well-organised monolith with clear internal boundaries. If a component later needs its own scaling or its own team, the boundary is already there to cut along. Splitting a tidy monolith is a normal week's work. Merging premature microservices is not.

"Well-organised" is doing real work in that sentence. It means modules grouped by domain rather than by technical layer, each with a deliberate public interface, and no reaching into another module's tables directly. That last rule is the one that makes a future split possible: shared database access across boundaries is what turns a clean module into something welded in place.

This is the shape behind most of the SaaS platforms we build — one deployable, several well-fenced domains inside it, and the option to extract one later kept open rather than exercised on day one.

The operational floor you need on day one

A small amount of infrastructure is worth having before the first customer, because each item is far harder to add under load than at rest.

Migrations that run automatically and in order, so nobody applies schema changes by hand. A background job runner, because the first email, export or webhook retry will otherwise block a web request. Structured logs with the tenant and request id attached, so a support question becomes a query rather than an archaeology exercise. And backups you have actually restored from once, since an untested backup is a belief rather than a plan.

None of this is architecture in the interesting sense, and all of it is cheaper before there's traffic. It's also the part that determines whether your first production incident lasts twenty minutes or a day.

The question we ask before all of this

"What breaks first if this succeeds?" If the answer is a specific query, we design for that query. If it's a background job, we design for that job. If nobody can answer, the product is early enough that architecture isn't the risk — shipping is.

That's the same instinct behind how we choose the rest of the stack: let a named constraint make the decision, and treat everything else as reversible. We wrote up that process in how we pick the right foundation for your product.

SYS — Common questions

When does multi-tenant SaaS actually need a database per tenant?

When a requirement forces it — regulated data, a contractual isolation guarantee, or data residency in a specific region. Those are things a customer or a regulator puts in writing. If the driver is instead a general sense that separate databases feel safer, a shared database with row-level security gives you enforceable isolation at a fraction of the operational cost.

Should we build authentication ourselves or use a provider?

Use a provider or a well-maintained library for the mechanics: password storage, sessions, MFA and SSO. Own the parts specific to your product — how accounts and organisations relate, and what each role can do. That split gives you the security track record of something battle-tested and keeps the model that's genuinely yours under your control.

Do we need microservices to scale a SaaS product?

No. Microservices solve an organisational problem — several teams needing to deploy independently — not a performance one. A well-structured monolith on decent hardware handles far more load than most products ever see. Split a service out when a specific component needs its own scaling profile or its own team, and not before.

SYS — Keep reading

Ready to ship something great?

Tell us about your product and we'll reply within one business day — no sales deck, just a straight answer on scope and timeline.