Multi-Tenant Laravel on Ubuntu 18.04 with NGINX, MariaDB, and PHP 7.3

If you navigate to your domain, you should see the Laravel welcome page.

Configuring LaravelNow that we have a working Laravel instance, navigate to the root directory of the app and edit the file named .

env:cd /var/www/html/app/nano .

envWe will need to change a few values here.

Listed below are changes:.

APP_URL=http://xyz.

com.

DB_CONNECTION=systemDB_DATABASE=laravelDB_USERNAME=laravelDB_PASSWORD=PasswordHere.

Below are additions:LIMIT_UUID_LENGTH_32=trueTENANT_URL_BASE=xyz.

comTENANCY_DATABASE_AUTO_DELETE=trueTENANCY_DATABASE_AUTO_DELETE_USER=trueInstalling the multi-tenant packageAfter saving the .

env file, run the following composer command to install the multi-tenant package:composer require "hyn/multi-tenant" 5.

4.

*That last line, composer require "hyn/multi-tenant" 5.

4.

*, tells composer that we need the multi-tenant package, but we only want versions greater than or equal to 5.

4, and versions less than 5.

5.

Generally, when the developers of packages only change that last number in a release, nothing breaks, so we want to restrict to that range for safety.

Run the following commands to setup Laravel’s built-in auth routes / templates / migrations, as well as to publish the multi-tenancy configuration:php artisan make:authphp artisan vendor:publish –tag=tenancyModify the database configuration file at config/database.

php by changing the key of the 'mysql' connection to 'system' (on around line 46, inside the array keyed 'connections').

Setting Up Multi-TenancyConfiguration changesModify the tenancy configuration file at config/tenancy.

php by setting 'update-app-url' to true.

EnforceTenancy MiddlewareRun the following command to create a new middleware called EnforceTenancy:php artisan make:middleware EnforceTenancyThis will create a new file in app/Http/Middleware called EnforceTenancy.

php.

Modify it, adding the following to a new line right before return $next($request); in the handle function:Config::set('database.

default', 'tenant');Additionally, add the following on a new line directly below use Closure;:use IlluminateSupportFacadesConfig;HTTP Kernel ModificationsAdd the following line in app/Http/Kernel.

php to the $routeMiddleware array:'tenancy.

enforce' => AppHttpMiddlewareEnforceTenancy::class,Tenant MigrationsCreate a new directory in the database/migrations folder called tenant and move all migrations that are tenant-specific to this folder.

For our purposes, this includes all files not starting with tenancy_ (so the two auth migrations):mkdir /var/www/html/app/database/migrations/tenantmv /var/www/html/app/database/migrations/2014_10_12* /var/www/html/app/database/migrations/tenant/You can now run the system migrations:php artisan migrateUsesTenantConnection TraitFor any model that should uses the tenant-specific database, ensure that the model uses the UsesTenantConnection trait.

Modify the User model at app/User.

php by adding the following line directly below use IlluminateFoundationAuthUser as Authenticatable;:use HynTenancyTraitsUsesTenantConnection;and replace the line use Notifiable; with the following:use Notifiable, UsesTenantConnection;UsesSystemConnection TraitAny model that should instead use the system database (not tenant-specific), ensure that the model uses the UsesSystemConnection trait.

Making ChangesCreating a Tenant helper classCreate a new file at app/Tenant.

php.

The contents should be as follows:<?phpnamespace App;use IlluminateSupportFacadesArtisan;use HynTenancyEnvironment;use HynTenancyModelsHostname;use HynTenancyModelsWebsite;use HynTenancyContractsRepositoriesHostnameRepository;use HynTenancyContractsRepositoriesWebsiteRepository;use IlluminateSupportFacadesLog;/** * @property Website website * @property Hostname hostname */class Tenant{ public function __construct(Website $website = null, Hostname $hostname = null) { $this->website = $website ??.$sub->website; $this->hostname = $hostname ??.$sub->websites->hostnames->first(); } public function delete() { app(HostnameRepository::class)->delete($this->hostname, true); app(WebsiteRepository::class)->delete($this->website, true); } public static function create($fqdn): Tenant { if (static::tenantExists($fqdn)) { $hostname = app(HostnameRepository::class)->findByHostname($fqdn); $website = $hostname->website; Log::info('Was asked to create tenant for FQDN ' .

$fqdn .

' but a tenant with this FQDN already exists; returned existing tenant'); return new Tenant($website, $hostname); } // Create New Website $website = new Website; app(WebsiteRepository::class)->create($website); // associate the website with a hostname $hostname = new Hostname; $hostname->fqdn = $fqdn; // $hostname->force_https = true; app(HostnameRepository::class)->attach($hostname, $website); // make hostname current app(Environment::class)->tenant($website); return new Tenant($website, $hostname); } public static function tenantExists($name) { return Hostname::where('fqdn', $name)->exists(); }}This will allow for an easy interface to create new tenants.

Creating a MakeTenant commandMost guides for multi-tenancy that I have read integrate the creation of tenants into the registration of specific users.

Thus, the creation of any one user would result in a new tenant being created.

It seems more intuitive to first create a tenant, and then allow users to register for that specific tenant by using a registration form at said tenant’s URL.

To enable this, let’s create an artisan command that will allow us to make tenants!php artisan make:command CreateTenantRunning this command will create a file at app/Console/Commands/CreateTenant.

php.

Let's modify that file.

Add the following line right below use IlluminateConsoleCommand;:use AppTenant;Change the signature of the command to:protected $signature = 'tenancy:make-tenant {fqdn}';and the description to:protected $description = 'Create a new tenant';Add the following to the handle() method:$fqdn = $this->argument('fqdn') .

'.

' .

env('TENANT_URL_BASE');Tenant::create($fqdn);This will allow us to create tenants using artisan like so:php artisan tenancy:make-tenant test…which would create the tenant test.

xyz.

com.

Pretty cool, eh?You can validate this by checking the hostnames table in your system database to see if test.

xyz.

com was created!Changing routesNow that we have all of the required tenancy changes in place, we need to alter our routes to support these changes.

In our setup, an administrative user is required to run the make-tenant command before a user can navigate to a given tenant site.

Because of this, it should not be possible for a user to log in or register from the main site (the site that doesn’t have a tenant associated with it).

To make this happen, we need to set up our routes file and modify some templates!Change your routes/web.

php file to look like this:<?phpRoute::domain('xyz.

com')->group(function () { Route::get('/', function () { return view('welcome'); }); Route::any('{any}', function () { abort(404); })->where('any', '.

*');});Auth::routes();Route::get('/home', 'HomeController@index')->name('home');Route::get('/', 'HomeController@index');Fixing registrationRegistration is broken!.By default, the registration controller checks to ensure that the registering user’s email address is unique in the users table.

However, that validation doesn't pare down to the tenant database.

We need to change that!Modify app/Http/Controllers/Auth/RegisterController.

php and change the email address validation from:'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],to:'email' => ['required', 'string', 'email', 'max:255', 'unique:tenant.

users'],Changing the layout fileOn the main site’s page, we see the Login and Register link, which is not desired.

Modify the file resources/views/welcome.

blade.

php and remove the lines starting with @if (Route::has('login')) and ending at @endif at the same indentation level.

We don't want users to try to login or register from this page.

Wrapping UpCongratulations!.At this point, you have a fully functioning, multi-tenant supporting, multi-database managed Laravel app.

You can create new tenants by running php artisan tenancy:make-tenant hello, for instance, to create a new tenant at hello.

xyz.

com.

To register a user, navigate to hello.

xyz.

com (of course, substituting xyz.

com with your real domain) and click Register.

What you can do from here is unlimited, and I have to stop somewhere.

Go make something great :-)Sources of inspiration:The multi tenant saas toolkit for LaravelTenancy allows you to easily scaffold a multi-tenant SaaS platform on top of the Laravel framework.

laravel-tenancy.

comFull-featured multi-tenant Laravel app from scratchHave you ever wondered where to get started with writing a robust multi-tenant Laravel app?.Wondered how to organize…blog.

usejournal.

comhttps://medium.

com/faun/laravel-multi-tenant-app-setup-part-0-ee4c730f4c2a.. More details

Leave a Reply