Laravel Multitenancy
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.
composer require stancl/tenancy
Then run
php artisan tenancy:install
This will create config/tenancy.php
, routes/tenant.php
, app/Providers/TenancyServiceProvider.php
and some new migration tables
Then add
// config/app.php
App\Providers\TenancyServiceProvider::class,
If there’s already a migration update the user migration
Schema::create('users', function (Blueprint $table) {
...
$table->string('domain')->unique();
...
});
Then run
php artisan migrate:fresh
Make a Tenant model
php artisan make:model Tenant
namespace App;
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 update
// config/tenancy.php
'tenant_model' => \App\Models\Tenant::class,
Update the RouteServiceProvider
// RouteServiceProvider
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', []);
}
Then update in boot() of RouteServiceProvider
// comment $this->routes(function () {}); add this instead
$this->routes(function () {
$this->mapApiRoutes();
$this->mapWebRoutes();
});
Add to .env
// localhost or domain tenant.test
APP_CENTRAL_DOMAIN=localhost
We will create tenants after the user registers or you can use Tinker.
$user = User::create(['name' => 'madindo','email' => '[email protected]', 'password' => bcrypt('madindo123'), 'domain' => 'madindo']);
// App\\Models\\User
public static function booted() {
static::created(function($user) {
$user_tenant = Tenant::create(['id' => $user->domain]);
$user_tenant->domains()->create(['domain' => $user->domain . '.' . env('APP_CENTRAL_DOMAIN')]);
});
}
For local development, you may use *.localhost
domains (like foo.localhost
) for tenants. On many operating systems, these work the same way as localhost
.
Try to access {user_domain}.tenant.test / {user_domain}.localhost
This is the start of multi-tenancy, there’s a lot more to develop for our custom app.
php artisan make:migration create_settings_table --path=database/migrations/tenant
Schema::create('settings', function (Blueprint $table) {
$table->string('key')->primary();
$table->text('value')->nullable();
$table->timestamps();
});
php artisan tenants:migrate
This will create a new folder in migration called tenant with migrations specifically for tenants.
Let’s make the model
php artisan make:model Tenant/Setting
namespace App\\Models\\Tenant;
use Illuminate\\Database\\Eloquent\\Factories\\HasFactory;
use Illuminate\\Database\\Eloquent\\Model;
class Setting extends Model
{
use HasFactory;
protected $keyType = 'string';
protected $primaryKey = 'id';
}
[Update 14/09/2024]
Redirect when accessing the wrong subdomain
Find TenantServiceProvider.php find method boot() add this
// Handling tenant identification failure
InitializeTenancyByDomain::$onFail = function ($exception, $request, $next) {
return redirect(env('APP_URL'));
};