DEV Community

Cover image for Architecting a B2B SaaS: Multi-Tenancy Made Simple
edmondgi
edmondgi

Posted on

Architecting a B2B SaaS: Multi-Tenancy Made Simple

Building business software (SaaS) is different from consumer apps.

You aren't just managing users; you are managing Teams (or Organizations/Tenants).

In this tutorial, we will build "TaskFlow," a Trello-like project management tool where:

  1. Users can belong to multiple Organizations (e.g., "Work", "Side Project").
  2. Each Organization has its own Projects and Billing.
  3. Data must be strictly isolated (Tenant A cannot see Tenant B's data).

Phase 1: The Multi-Tenant Data Model

The database schema is the foundation of multi-tenancy.
Every resource must "belong" to an organization.

// schema.prisma

model Organization {
  id        String   @id @default(uuid())
  name      String
  slug      String   @unique // e.g., taskflow.com/acme-corp
  members   OrgMember[]
  projects  Project[]
}

model OrgMember {
  id        String   @id @default(uuid())
  orgId     String
  userId    String   // The Rugi Auth User ID
  role      String   // 'OWNER', 'MEMBER', 'GUEST'

  organization Organization @relation(fields: [orgId], references: [id])

  @@unique([orgId, userId]) // User can join an org only once
}

model Project {
  id        String   @id @default(uuid())
  orgId     String
  title     String
  tasks     Task[]

  // CRITICAL: Linking project to Org ensures isolation
  organization Organization @relation(fields: [orgId], references: [id])
}
Enter fullscreen mode Exit fullscreen mode

Phase 2: Resolving Identity vs. Context

Here is the conceptual leap:
Identity is "Who are you?" (I am Alice).
Context is "Where are you?" (I am currently working in Acme Corp).

Many developers make the mistake of creating a new user account for every organization (alice@acme.com, alice@beta.com). This is a nightmare for users.

The Better Way (Powered by Rugi Auth):

  1. Alice has One Identity in Rugi Auth.
  2. Your application handles the Context.

Step 1: Authentication (Identity)

Alice logs in via Rugi Auth. We get her userId.

Step 2: Tenant Resolution (Context)

When Alice visits app.taskflow.com/acme-corp/dashboard:

  1. Middleware Check: Is Alice authenticated? (Yes, valid Token).
  2. Membership Check: Does OrgMember table have an entry for userId: Alice AND orgId: AcmeCorp?
    • Yes: Proceed.
    • No: 403 Forbidden.
// middleware/tenant.ts
import { prisma } from './db';

export const requireTenantMembership = async (req, res, next) => {
  const userId = req.user.id;
  const orgSlug = req.params.orgSlug; // from URL

  const member = await prisma.orgMember.findFirst({
    where: {
      userId,
      organization: { slug: orgSlug }
    }
  });

  if (!member) {
    return res.status(403).json({ error: "You are not a member of this organization" });
  }

  // Attach membership info to request for downstream use
  req.memberRole = member.role;
  req.orgId = member.orgId;
  next();
};
Enter fullscreen mode Exit fullscreen mode

Phase 3: Invitation System (Growth Engine)

SaaS grows via invites. "Alice invites Bob to Acme Corp."

  1. Frontend: Alice enters Bob's email in the dashboard.
  2. Backend:
    • Check if Bob already has a Rugi Auth account?
    • If Yes: Add entry to OrgMember. Send email "You've been added!".
    • If No: create a "Pending Invite".

Using Rugi Auth's Registration API:
You can pre-provision users or simply send them to your registration page.
When Bob finally registers with that email, your webhook (or post-registration hook) detects the pending invite and automatically adds him to the organization.


Phase 4: Billing & Admin (Global Scope)

You, as the SaaS builder, need a "God Mode" to see who is paying.

In Rugi Auth, define a role SAAS_SUPER_ADMIN in your app configuration.

// routes/billing.ts

// Only for YOU
router.get('/all-tenants', requireRole('SAAS_SUPER_ADMIN'), async (req, res) => {
  const allOrgs = await prisma.organization.findMany({
    include: { _count: { select: { members: true } } }
  });
  res.json(allOrgs);
});
Enter fullscreen mode Exit fullscreen mode

This keeps your "Super Admin" logic completely separate from the "Organization Owner" logic, preventing the accidental "Customer A sees Customer B" bugs.

Summary

Building a SaaS is about rigorous data isolation.

  • Rugi Auth handles the global "Who is this person?" question.
  • Your App handles the huge "Which team are they on?" matrix.

This separation allows your SaaS to support users who belong to 50 different teams with a single login, a feature users love and expect from modern tools.

Top comments (0)