SYS://VISION.ACTIVE
VIEWPORT.01
LAT 28.0222° N
SIGNAL.NOMINAL
VISION Loading
Back to Blog

Multi-Tenancy in Laravel: Architecture Patterns and Implementation

Shane Barron

Shane Barron

Laravel Developer & AI Integration Specialist

Understanding Multi-Tenancy

Multi-tenant applications serve multiple customers (tenants) from a single codebase while keeping their data isolated. Think SaaS platforms where each company gets their own workspace. The architectural decisions you make here impact security, performance, and operational complexity.

Tenancy Strategies

1. Database Per Tenant

Each tenant gets their own database. Maximum isolation, but highest operational overhead.

class TenantDatabaseManager
{
    public function createDatabase(Tenant $tenant): void
    {
        DB::statement("CREATE DATABASE tenant_{$tenant->id}");

        config(['database.connections.tenant' => [
            'driver' => 'mysql',
            'database' => "tenant_{$tenant->id}",
            // ... other config
        ]]);

        Artisan::call('migrate', [
            '--database' => 'tenant',
            '--path' => 'database/migrations/tenant',
        ]);
    }
}

2. Schema Per Tenant (PostgreSQL)

Each tenant gets their own schema within a shared database. Good balance of isolation and efficiency.

3. Row-Level Isolation

All tenants share tables, with a tenant_id column for isolation. Most efficient, requires careful implementation.

trait BelongsToTenant
{
    protected static function bootBelongsToTenant(): void
    {
        static::creating(function ($model) {
            if (!$model->tenant_id && auth()->check()) {
                $model->tenant_id = auth()->user()->tenant_id;
            }
        });

        static::addGlobalScope('tenant', function ($query) {
            if (auth()->check() && !auth()->user()->is_super_admin) {
                $query->where('tenant_id', auth()->user()->tenant_id);
            }
        });
    }
}

Using Spatie's Tenancy Package

For complex multi-tenancy needs, consider spatie/laravel-multitenancy:

class Tenant extends Model implements TenantContract
{
    use UsesTenantConnection;

    public static function current(): ?static
    {
        return app(TenancyManager::class)->currentTenant();
    }
}

// Tenant identification middleware
class IdentifyTenant
{
    public function handle(Request $request, Closure $next)
    {
        $tenant = Tenant::where('domain', $request->getHost())->first();

        if (!$tenant) {
            abort(404);
        }

        app(TenancyManager::class)->setCurrentTenant($tenant);

        return $next($request);
    }
}

Queue Considerations

Queued jobs need tenant context:

class TenantAwareJob implements ShouldQueue
{
    use SerializesTenantId;

    public function handle(): void
    {
        // Tenant context is automatically restored
        $this->doWork();
    }
}

trait SerializesTenantId
{
    public ?int $tenantId;

    public function __serialize(): array
    {
        return array_merge(parent::__serialize(), [
            'tenantId' => Tenant::current()?->id,
        ]);
    }

    public function __unserialize(array $data): void
    {
        parent::__unserialize($data);

        if ($this->tenantId) {
            Tenant::find($this->tenantId)?->makeCurrent();
        }
    }
}

Testing Multi-Tenant Code

public function test_user_can_only_see_own_tenant_data(): void
{
    $tenant1 = Tenant::factory()->create();
    $tenant2 = Tenant::factory()->create();

    $user1 = User::factory()->for($tenant1)->create();
    $order1 = Order::factory()->for($tenant1)->create();
    $order2 = Order::factory()->for($tenant2)->create();

    $this->actingAs($user1)
        ->get('/orders')
        ->assertSee($order1->id)
        ->assertDontSee($order2->id);
}

Conclusion

Choose your tenancy strategy based on your isolation requirements, operational capacity, and scale expectations. Row-level isolation is most efficient for most SaaS applications, but database-per-tenant might be required for compliance or enterprise customers.

Share this article
Shane Barron

Shane Barron

Strategic Technology Architect with 40 years of experience building production systems. Specializing in Laravel, AI integration, and enterprise architecture.

Need Help With Your Project?

I respond to all inquiries within 24 hours. Let's discuss how I can help build your production-ready system.

Get In Touch