Home / Insights / Multi-Tenant SaaS Architecture
Production Architecture Teardown

Multi-Tenant B2B SaaS Architecture in Laravel: A Production Teardown of MyProperty

Architectural Teardown Context

Unlike generic architectural tutorials built on theoretical "Todo apps", this teardown documents the real-world engineering decisions behind MyProperty (property.arhantsolutions.com), our commercial multi-tenant property and facility management SaaS operating in active production.

  • Stack: Laravel 11, PHP 8.3, PostgreSQL 16, Redis 7, Filament PHP v3, Livewire 3, Tailwind CSS.
  • Scale Profile: Hundreds of residential and commercial societies, thousands of daily units, high-concurrency payment collections, and sub-45ms p95 response times.
  • Core Objective: Zero data leakage between corporate tenants, instant automated onboarding, seamless billing reconciliation, and single-command CI/CD deployments.
Building a multi-tenant B2B platform or SaaS MVP? Explore our 8–12 Week SaaS MVP Model →

1. Tenant Isolation: Single-Database vs. Multi-Database

The first architectural fork every SaaS engineering lead faces is choosing between:

  1. Multi-Database (Database-per-tenant): Each new subscriber provisions a distinct PostgreSQL database or schema.
  2. Single-Database (Column-scoped tenant isolation): Every table stores a foreign key tenant_id with strict application-level and database-level enforcement.

In MyProperty, we selected Single-Database Multi-Tenancy with PostgreSQL Composite Indexing and Scoped Constraints. While multi-database setups sound appealing for enterprise isolation in pitch decks, in production they introduce crippling architectural friction:

  • Migration Hell: Running schema migrations across 500 tenant databases requires distributed orchestration tools, risks half-migrated states when one connection times out, and inflates deployment times from 45 seconds to over an hour.
  • Connection Pool Exhaustion: Each PostgreSQL instance requires dedicated connection overhead. Scaling to 1,000 tenants saturates server memory with idle connections.
  • Reporting Impossibility: Aggregating platform-wide metrics (such as aggregate platform GMV or churn cohort analytics) requires slow, fragile cross-database union queries.

Enforcing Bulletproof Isolation in Laravel

To guarantee zero cross-tenant contamination in a single-database design, we built an unyielding 3-layer safety net:

<?php

namespace App\Models\Traits;

use App\Models\Tenant;
use App\Scopes\TenantScope;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

trait BelongsToTenant
{
    public static function bootBelongsToTenant(): void
    {
        // 1. Automatically scope all SELECT, UPDATE, DELETE queries
        static::addGlobalScope(new TenantScope());

        // 2. Automatically inject tenant_id on INSERT operations
        static::creating(function ($model) {
            if (!$model->tenant_id && auth()->check()) {
                $model->tenant_id = auth()->user()->tenant_id;
            }
        });
    }

    public function tenant(): BelongsTo
    {
        return $this->belongsTo(Tenant::class);
    }
}

Coupled with composite unique constraints in database migrations:

Schema::create('units', function (Blueprint $table) {
    $table->id();
    $table->foreignId('tenant_id')->constrained('tenants')->cascadeOnDelete();
    $table->string('unit_number');
    $table->string('block_name');
    
    // Strict uniqueness within the tenant scope, allowing identical unit numbers across different tenants
    $table->unique(['tenant_id', 'block_name', 'unit_number']);
    
    // Critical composite index for sub-5ms query lookups
    $table->index(['tenant_id', 'created_at']);
});

2. Modular Admin Architecture: Filament PHP v3 Panels

Traditional SaaS admin panels built with custom React or Vue dashboards frequently swallow 60% of an engineering team's sprint budget on repetitive CRUD forms, pagination widgets, and table filtering.

For MyProperty, we leveraged Filament PHP v3 to architect two strictly segmented panels:

  1. The Central Superadmin Panel (/admin): Accessible only by platform operations leads to monitor global subscription health, manage feature flags, audit tenant activity logs, and provision new society instances.
  2. The Tenant Portal (/app or subdomain.domain.com): Customized for society managers, accountants, and facility staff with scoped access to their society's residents, maintenance ledgers, and compliance filings.

Filament's native tenant() registration enables automatic context switching:

public function panel(Panel $panel): Panel
{
    return $panel
        ->id('tenant')
        ->path('portal')
        ->tenant(Society::class, slugAttribute: 'slug')
        ->tenantRegistration(RegisterSociety::class)
        ->tenantProfile(EditSocietyProfile::class)
        ->middleware([
            InitializeTenancyBySubdomain::class,
            EnsureTenantIsActive::class,
        ]);
}

Whenever a user accesses ashoka-heights.property.arhantsolutions.com, Filament automatically binds the Society instance to the container, isolating all navigation resources, widgets, and form actions to that exact organization.

3. High-Concurrency Database Design & Indexing

During month-end billing runs in MyProperty, hundreds of societies generate tens of thousands of maintenance bills, utility debit notes, and payment receipts concurrently. Without strategic database indexing, query response times quickly degrade from milliseconds to timeout thresholds.

Here are the core PostgreSQL optimization principles implemented in our production schema:

  • Composite Leading Indexes: Every analytical query in a multi-tenant platform begins with WHERE tenant_id = ?. Consequently, single-column indexes on status or created_at are useless. All indexes must be composite with tenant_id as the leading column: INDEX (tenant_id, status, payment_date).
  • Partial Indexes for Active Records: 90% of user queries target pending or overdue bills. We create PostgreSQL partial indexes to minimize index memory footprint:
    CREATE INDEX idx_invoices_unpaid ON invoices (tenant_id, due_date) WHERE status = 'UNPAID';
    This reduces index RAM consumption by over 75% compared to indexing the entire historical transaction archive.
  • PostgreSQL Read-Replica Splitting: In our config/database.php, heavy analytical queries (such as society audit reports or annual balance sheet exports) are routed to dedicated read-replicas, keeping the primary writer connection pristine for transactional payments.

4. Automated Subscription Billing & Webhook Idempotency

A catastrophic flaw in naive SaaS implementations is handling payment gateway webhooks synchronously without distributed locks. In real-world networks:

  • Payment gateways (Stripe, Razorpay) frequently dispatch duplicate webhook notifications for a single charge event.
  • Network retries arrive milliseconds apart on different load-balanced servers.
  • Without atomic locks, two parallel workers can mark an invoice paid twice or double-credit a client's account balance.

Our Idempotent Webhook Engine

In MyProperty, all incoming webhooks are validated with cryptographic signatures, immediately queued into Redis, and processed with atomic distributed locks:

<?php

namespace App\Jobs;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class ProcessPaymentWebhookJob
{
    public function handle(): void
    {
        $eventId = $this->payload['id'];
        
        // Acquire an atomic lock for 15 seconds to prevent race conditions
        $lock = Cache::lock("webhook_lock:{$eventId}", 15);

        if (!$lock->get()) {
            Log::info("Webhook {$eventId} is already being processed by another worker.");
            return;
        }

        try {
            // Check if already processed in our database ledger
            if (ProcessedWebhook::where('event_id', $eventId)->exists()) {
                return;
            }

            // Execute transactional ledger update
            DB::transaction(function () {
                $this->applyPaymentToInvoice();
                ProcessedWebhook::create(['event_id' => $this->payload['id']]);
            });
        } finally {
            $lock->release();
        }
    }
}

5. Hierarchical Multi-Tenant RBAC

Enterprise property management requires granular, matrix-driven permissions. In MyProperty, access control spans 4 distinct operational tiers:

  • Society Chairman / Committee: Full access to financial budgets, committee voting, vendor contracts, and bank reconciliations.
  • Society Accountants: Read/Write access to ledgers, debit/credit notes, and invoicing, with zero access to member privacy settings.
  • Facility Staff & Security Guards: Streamlined mobile-optimized interface restricted solely to visitor check-ins, package deliveries, and emergency alerts.
  • Residents & Unit Owners: Self-service portal to view ledger statements, raise maintenance tickets, and initiate instant UPI / card payments.

We implemented this using a customized multi-tenant extension of spatie/laravel-permission. Every role and permission record is scoped to the active tenant_id, ensuring that a custom "Facility Manager" role configured in Society A has zero jurisdiction or naming collision with Society B.

6. Production SaaS Architecture Checklist

Before launching any B2B SaaS platform into production, ensure your engineering team has verified these 7 architectural criteria:

  • Global Scoping Test Coverage: Automated tests that explicitly verify querying without tenant context returns zero records or throws an unauthorized exception.
  • Composite Unique Keys: All business keys (usernames, invoice numbers, unit tags) must be uniquely indexed against tenant_id.
  • Subdomain DNS Sanitization: Strict regex validation preventing reserved subdomains (admin, api, billing, mail) from being claimed during tenant registration.
  • Distributed Redis Locks: All webhook listeners and recurring cron tasks guarded by atomic Redis locks.
  • Encrypted Tenant Credentials: Payment gateway keys, SMTP credentials, and SMS tokens encrypted at rest via Laravel's Crypt::encryptString().
  • Automated Database Backups: Point-in-time recovery (PITR) enabled on primary databases with nightly encrypted snapshots pushed to separate cloud regions.
  • Zero-Downtime Deployment: CI/CD workflows using atomic symlinks or blue/green container deployments to eliminate request drops during schema updates.

Architecting Your Next SaaS Platform?

Arhant Solutions builds enterprise B2B SaaS platforms and custom digital suites with 100% Day-1 source-code ownership. Talk directly with our solutions architects who designed and deployed MyProperty and Arhant ERP.

Schedule SaaS Architecture Session Estimate Your SaaS Scope & Cost →