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.
1. Tenant Isolation: Single-Database vs. Multi-Database
The first architectural fork every SaaS engineering lead faces is choosing between:
- Multi-Database (Database-per-tenant): Each new subscriber provisions a distinct PostgreSQL database or schema.
- Single-Database (Column-scoped tenant isolation): Every table stores a foreign key
tenant_idwith 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:
- 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. - The Tenant Portal (
/apporsubdomain.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 onstatusorcreated_atare useless. All indexes must be composite withtenant_idas 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:
This reduces index RAM consumption by over 75% compared to indexing the entire historical transaction archive.CREATE INDEX idx_invoices_unpaid ON invoices (tenant_id, due_date) WHERE status = 'UNPAID'; - 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.