Sabbir Ahmed

Sabbir Ahmed

1 month ago

Laravel Multi Tenancy Tutorial: Build Multi-Tenant Architecture with Package

Multi tenancy is a design pattern in which you can serve multiple users from a single codebase. This trick is very smart, because you dont have to use different setup for different customers. Rather you can develop one single application, and that same code can be used to serve different customers. On the other hand, on a single tenant application, we have to use multiple code base and multiple database. Today in this article, we will see how to apply multi tenancy in a laravel application.

Implementing Multi-Tenancy in Laravel: A Step-by-Step Guide


To apply multi tenancy, we will see a package called stancl/tenancy. Its one of the most popular and powerful package to apply tenancy in your application. 

Lets assume you have set up a laravel application, with models like users, posts and comments in it. Lets say you can access your app in local environment at  localhost:8000. Now we will see how we can make multiple tenants here. 

Lets start with installing the package first. 


composer require stancl/tenancy
php artisan tenancy:install

Then add this provider under config/app.php


return [
    App\Providers\AppServiceProvider::class,
    App\Providers\TenancyServiceProvider::class, // <-- here
];

Then please run migration: 


php artisan migrate

  • Create a tenant model 


Now we need to create a custom tenant model, under App\Models\Tenant.php: 


<?php

namespace App\Models;

use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\Concerns\HasDatabase;
use Stancl\Tenancy\Database\Concerns\HasDomains;

class Tenant extends BaseTenant implements TenantWithDatabase
{
    use HasDatabase, HasDomains;
}

Then, lets add the model to config/tenancy.php



<?php

declare (strict_types = 1);

use Stancl\Tenancy\Database\Models\Domain;
return [
    'tenant_model' => App\Models\Tenant::class, //add the custom model here 
    'id_generator' => Stancl\Tenancy\UUIDGenerator::class,

    'domain_model' => Domain::class,

  • Prepare the tenant migrations


Now, lets assume our 3 tables, users, posts, comments are already migrated to central database. Now we need to migrate those for tenants too. Under database/migrations, you will find a new folder called tenant. Here you will have to insert all the migrations you want to be available for the tenants. 

Lets copy the migration files for user, posts, and comments and paste copies to tenant folder. The purpose is, whenever we create a new tenant, these migrations will be run automatically, and a new copies of these tables will be created under tenant database too. 

Please have a look at Providers/TenancyServiceProvider.php, here we can control the actions based on different event occurred on tenants. Lets have a look Events\TenantCreated class, here Database and migration creation actions are defined already as jobs. We can also have our own actions added as well as required: 


class TenancyServiceProvider extends ServiceProvider
{
    // By default, no namespace is used to support the callable array syntax.
    public static string $controllerNamespace = '';

    public function events()
    {
        return [
            // Tenant events
            Events\CreatingTenant::class => [],
            Events\TenantCreated::class => [
                JobPipeline::make([
                    Jobs\CreateDatabase::class,
                    Jobs\MigrateDatabase::class,
                    // Jobs\SeedDatabase::class,

                    // Your own jobs to prepare the tenant.
                    // Provision API keys, create S3 buckets, anything you want!
 

  • Define central domains


Now we need to define our central domains, which the app will use to differentiate the routes under central or tenant domains. 

You can define central domains under config/tenancy.php


'central_domains' => [
    '127.0.0.1',
    'localhost',
],

  • Protect central routes


Based on the central domains, we can configure our central routes in RouteServiceProvider. Please make the following changes in the file: 


    public function boot()
    {
        $this->configureRateLimiting();

        $centralDomains = $this->getCentralDomains();

        // apply middleware only on central domains
        foreach ($centralDomains as $key => $centralDomain) {
            $this->routes(function () use ($centralDomain) {
                Route::prefix('api')
                    ->middleware('api')
                    ->namespace($this->namespace)
                    ->domain($centralDomain)
                    ->group(base_path('routes/api.php'));

                Route::middleware('web')
                    ->namespace($this->namespace)
                    ->domain($centralDomain)
                    ->group(base_path('routes/web.php'));
            });
        }
    }

    protected function getCentralDomains()
    {
        return config('tenancy.central_domains');
    }

  • What about tenant routes? 


Yeah, we have configured central routes, but to manage tenant routes, you will find a new file under routes/tenant.php. Here you can put all the tenant routes as needed: 


Route::middleware([
    'web',
    InitializeTenancyByDomain::class,
    PreventAccessFromCentralDomains::class,
])->group(function () {
    Route::get('/', function () {
        return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id');
    });
});

Lets have this code in the file so that we can check our routes are working well or not: 


Route::get('/', function () {
        dd(tenant(‘id’)); //get model
    return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id');
})->name(‘tenant.home’);

  • Lets add some tenants


Yeah, all the hardworks are done. Now its time to add some tenants in the system. Lets add with tinker:


php artisan tinker
$tenant = App\Models\Tenant::create(['id' => 'john']);
$tenant->domains()->create(['domain' => john.localhost']);

The code will create a new tenant along with the domains in the database. You can check at http://john.localhost, which will trigger the route named ‘tenant.home’ defined in tenant.php. It should dump and show ‘id’ of the tenant you have just created named john. From here on, you can add all the routes as required in this file. Also check your central routes, they should work as usual. 

So congratulations, you just created a multi-tenant application with same source code to serve separate user base.

Demystifying Laravel Multi-Tenancy: A Database Deep Dive


Multi tenany in laravel is an amazing functionality where we can use the same source code to serve different user base. It saves your time and money to build such thing rather than building separate applications with similar kind of requirements. We have seen how we can apply multi tenancy in a laravel application. Now we will try to understand the database and inner mechanism of this package to have a better insight. 

Lets create these 2 tenants, please use following tinker commands: 


$tenant1 = App\Models\Tenant::create(['id' => 'john']);
$tenant1->domains()->create(['domain' => john.localhost']);

$tenant2 = App\Models\Tenant::create(['id' => 'bruce']);
$tenant2->domains()->create(['domain' => 'bruce.localhost']);

This command will create 2 new tenants in your database. And you should now be able to access your tenants by accessing these domains 


 john.localhost
bruce.localhost

  • Understanding databases


After we installed the stancl/tenancy package and ran the migration, the app should have migrated 2 new tables in the central database, named tenants and domains. In the tenants, the new tenants created are stored with their id we provide, as given above. If we dont provide any id, it will generate a random string automatically as an ID for the tenant. 

As we have provided john and bruce as ID, you should be able to see 2 new records are added as tenants. Also, there is another column named “data”, where the ‘tenancy_db_name’ is provided. That means, there is also another database which is created with that ‘tenancy_db_name’. You should find 2 new database for 2 new IDs are created and named like tenantjohn and tenantbruce

Also you will see another table named domains in the central database, where corresponding domains based on tenant id are added as we have provided before. 

  • How would I make changes on the tenants?  


We can make the changes on the tenants as we do in the other models. Lets say you want to change the domain of the tenant. You can do as the following: 


use App\Models\Tenant;

$tenant = Tenant::find('john');
$tenant1->domains()->update(['domain' => john1.localhost']);

Also thats how you can delete a particular tenant from the records. You can run:


$tenant = Tenant::find('john');
$tenant->delete();

This will delete all the records from tenants, its domains and corresponding databases. 

Thats how you can run a CRUD operation with tenants and its databases. 

More Usage on Tenancy


  • Database Seeding for Tenants


You can see data for each tenant with defining the specific tenant in command. You can seed data for each tenant some default settings or sample content.


php artisan tenants:seed --tenant=1

Here, with tenants:seed command, you are seeding data for tenant ID 1 in database.

You can also define custom seeder for tenants like this: 


use Illuminate\Database\Seeder;

class TenantDataSeeder extends Seeder
{
    public function run()
    {
        // Insert sample data specific to the tenant
        DB::table('settings')->insert([
            'key' => 'default_language',
            'value' => 'en',
        ]);
    }
}

Make sure you have migrated settings table with required columns while creating the tenants. 

  • Event-Based Tenant Management


The stancl/tenancy package allows you to handle actions or events like TenantCreated, TenantDeleted and others. You can set automated actions like setup, cleanup or custom actions like sending notification, when these events are fired. 

Here is an example on how to send an Welcome email on Tenant creation: 


use Stancl\Tenancy\Events\TenantCreated;
use Illuminate\Support\Facades\Mail;
use App\Mail\TenantWelcomeMail;

Event::listen(TenantCreated::class, function ($event) {
    $tenant = $event->tenant;
    Mail::to($tenant->email)->send(new TenantWelcomeMail($tenant));
});

Similarly, when tenant is deleted, you can perform these cleanup to remove all the residuals left: 


use Stancl\Tenancy\Events\TenantDeleted;

Event::listen(TenantDeleted::class, function ($event) {
    // Custom cleanup actions for deleted tenant
    $tenant = $event->tenant;
    Storage::deleteDirectory("tenants/{$tenant->id}");
});

  • Centralized Configurations with Tenant Variables


In multi-tenancy, you would sometimes require tenant specific environment or configurations like timezones, currancy or other custom settings. You can store and retrieve these unique settings for each tenant too. No need to maintain any hard-coded value or separate config files. tenant() helper can help you to perform these actions efficiently which you can access across your application. Here is example how can configure timezone or currency: 


// Set tenant-specific configurations (e.g., timezone and currency)
tenant()->put(['timezone' => 'America/New_York', 'currency' => 'USD']);

// Usage in code, e.g., in a service or Blade view
config(['app.timezone' => tenant('timezone')]);
echo 'The currency for this tenant is ' . tenant('currency');

Thats how you can handle regional customizations without using or modifying any global application settings.

Summary


Tenancy or multi tenancy might seem a difficult topic to understand at first. But after playing with it for a while, you will see how amazing and smart it is to use this functionality for serving multiple users. I hope this tutorial has helped you to get a better understanding that how this package works in the database levels to manage the data. Please give a try with yourself and let me know your experience and thougths in the comments. 

Thats all for today. Wish you a great day. 

Comments

Login As User
Word Count: 0