Blog Detail

23

Nov
How to make a Laravel App Automatically Multi-tenant cover image

arrow_back How to make a Laravel App Automatically Multi-tenant

Multi-tenancy is the ability to provide your service to multiple users (tenants) from a single hosted instance of the application. This is contrasted with deploying the application separately for each user. This package is built around the idea that multi-tenancy usually means letting tenants have their own users who have their own resources, e.g. todo tasks. Not just users having tasks.

Types of multi-tenancy

There are two types of multi-tenancy:

  • single-database tenancy — tenants share one database and their data is separated using e.g. where tenant_id = 1 clauses.
  • multi-database tenancy — each tenant has his own database

This package lets you do both, though it focuses more on multi-database tenancy because that typically requires more work on the side of the package and less work on your side. Whereas for a single-database tenancy you’re provided with a class that keeps track of the current tenant and model traits and the rest is up to you.

Tenant identification

For your application to be tenant-aware, a tenant has to be identified. This package ships with many identification middleware classes. You may identify tenants by domain, subdomain, domain, OR subdomain at the same time, path, or request data.

A Quickstart

Here I’ll share some basic details that enable you to implement multi-database tenancy & domain identification. If you need a different implementation, then that’s definitely possible with this package and it’s very easy to refactor to a different implementation.

I suggest you follow this tutorial just to get things working so that you can work with this package. Then if you need to, you can refactor the details of the multi-tenancy implementation (e.g. single-database tenancy, request data identification, etc).

Installation

First, you’ve to require the package using composer for the installation:

composer require stancl/tenancy

Then, run the tenancy:install command:

php artisan tenancy:install

This will create a few files: migrations, config file, route file, and a service provider.

Next, you’ve to run the migrations:

php artisan migrate

Register the service provider in config/app.php.

Creating a tenant model

Next, you need to build a Tenant model. The package has a default Tenant model that has many features, but it strives to be mostly unopinionated and as such, you’ve to create a custom model to use domains & databases. Create the file app/Models/Tenant.php like this:

<?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;
}

Now you need to tell the package to utilize this custom model. Open the config/tenancy.php file and modify the line below:

'tenant_model' => \App\Models\Tenant::class,

Events

The TenancyServiceProvider file in your app/Providers directory maps tenancy events to listeners. By default, when a tenant is created, it runs a JobPipeline (a smart thing that’s part of this package) which makes sure that the CreateDatabase, MigrateDatabase, and optionally other jobs (e.g. SeedDatabase) are run sequentially. In other words, it creates & migrates the tenant’s database after he’s created and it does this in the correct order.

Central routes

Now you’ve to make a small change to the app/Providers/RouteServiceProvider.php file. Specifically, Make sure that central routes are registered on central domains only.

protected function mapWebRoutes()
{
    foreach ($this->centralDomains() as $domain) {
        Route::middleware('web')
            ->domain($domain)
            ->namespace($this->namespace)
            ->group(base_path('routes/web.php'));
    }
}

protected function mapApiRoutes()
{
    foreach ($this->centralDomains() as $domain) {
        Route::prefix('api')
            ->domain($domain)
            ->middleware('api')
            ->namespace($this->namespace)
            ->group(base_path('routes/api.php'));
    }
}

protected function centralDomains(): array
{
    return config('tenancy.central_domains');
}

Call these methods manually from your RouteServiceProvider’s boot() method, instead of the $this->routes() calls.

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

    $this->mapWebRoutes();
    $this->mapApiRoutes();
}

Central domains

Now you need to actually specify the central domains. A central domain is a domain that serves your central app content, e.g. the landing page where tenants sign up. Open the config/tenancy.php file and add them in:

'central_domains' => [
    'saas.test',
 // Add the ones that you use. I use this one with Laravel Valet.
],

If you’re using Laravel Sail, no changes are needed, default values are good to go:

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

Tenant routes

Your tenant routes will look like this by default:

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');
    });
});

These routes will only be accessible on tenant (non-central) domains, the PreventAccessFromCentralDomains middleware enforces that.

Next, you’ve to make a small change to dump all the users in the database, so that you can actually see multi-tenancy working. Now open the file routes/tenant.php and apply the modification below:

Route::get('/', function () {
    dd(\App\Models\User::all());
    return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id');
});

Migrations

Next, You’ve to move the user’s table migration (the file database/migrations/2014_10_12_000000_create_users_table.php or similar) to database/migrations/tenant. This will prevent the table from being created in the central database, and it will be instead created in the tenant database when a tenant is created.

Creating tenants

For testing purposes, You can create a tenant in tinker quickly.

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

Now we’ll create a user inside each tenant’s database:

App\Models\Tenant::all()->runForEach(function () {
    App\Models\User::factory()->create();
});

Trying it out

Now you can visit foo.localhost in our browser, Now replace localhost with one of the values of central_domains in the file config/tenancy.php as modified previously. You should see a dump of the users table where you see some users. If you visit bar.localhost, you should see a different user.

This package has a lot of different features and options. If you want to learn more you can visit its complete documentation on its Official Website and get source code on Github.

Published at : 23-11-2021

Author : Rizwan Aslam
AUTHOR
Rizwan Aslam

I am a highly results-driven professional with 12+ years of collective experience in the grounds of web application development especially in laravel, native android application development in java, and desktop application development in the dot net framework. Now managing a team of expert developers at Codebrisk.

Launch your project

Launch project