How to make any Laravel application multi-tenant in 5 minutes

We will be implementing a multi-database tenancy package of mine, stancl/tenancy into a simple Laravel blog example I found on GitHub.

The main feature of the package is that you don’t have to make any changes to your app’s code. After it identifies the tenant using the hostname, it automatically configures database, Redis, cache and the filesystem for that tenant.

This tutorial explains the basics of making an app multi-tenant using this package. For a real-world production application, you should read the documentation. It’s not long.

Installing the tenancy package

The package supports Laravel 5.7 and 5.8.

You will also need Redis and phpredis.

composer require stancl/tenancy

Adding middleware

Open app/Http/Kernel.php and make the Stancl\Tenancy\Middleware\InitializeTenancy middleware top priority, to make sure everything is configured for tenancy before any code is run.

protected $middlewarePriority = [ \Stancl\Tenancy\Middleware\InitializeTenancy::class, // ... ];

Making the routes multi-tenant

The package lets you have tenant routes in routes/tenant.php and shared routes in routes/web.php. When someone visits a tenant route, the InitializeMiddleware will configure everything for tenancy. If you have some content you don’t want to apply tenany for, such as landing pages, sign up pages, etc, put those into web.php.

Since we don’t have any shared content, we’ll just rename routes/web.php to routes/tenant.php and create an empty routes/web.php file.

Configuration

First you need to publish the configuration file of the package. Run

php artisan vendor:publish --provider='Stancl\Tenancy\TenancyServiceProvider' --tag=config

and you should see something along the lines of Copied File [...] to [/config/tenancy.php].

This lets us make changes to the configuration file of the tenancy package. I don’t want to use MySQL for this simple repository, so I will change the DB driver for the application in .env and for the package in config/tenancy.php:

// .env DB_CONNECTION=sqlite // config/tenancy.php 'database' => [ 'based_on' => 'sqlite', // ... ],

Creating a Redis connection

By default, the package uses Redis as the "central" storage — for storing data about tenants. Redis is ideal for this thanks to its high performance. You don’t really need to use a relational database for this.

Add this to database.redis config:

'tenancy' => [ 'host' => env('TENANCY_REDIS_HOST', '127.0.0.1'), 'password' => env('TENANCY_REDIS_PASSWORD', null), 'port' => env('TENANCY_REDIS_PORT', 6379), 'database' => env('TENANCY_REDIS_DB', 3), ],

Make sure you use a unique database number — Redis supports 16 databases which let you have multiple applications use the same Redis instance without any conflicts.

Creating tenants

We’ll create two tenants to see that the data separation works correctly. We’ll be using php artisan tinker to keep it simple. For your app, you should create a page that does this.

We’ll use these two subdomains:

  • tenant1.localhost
  • tenant2.localhost

Anything under the .localhost TLD is automatically redirected to 127.0.0.1, so we don’t have to make any changes to /etc/hosts.

Open php artisan tinker and run these two functions:

>>> tenant()->create('tenant1.localhost') => [ "uuid" => "e5611150-9a9e-11e9-8315-b9eb127de2b8", "domain" => "tenant1.localhost", ] >>> tenant()->create('tenant2.localhost') => [ "uuid" => "e8002ec0-9a9e-11e9-8095-51e64ce28359", "domain" => "tenant2.localhost", ]

Migrations

We’ll move all migrations from database/migrations to database/migrations/tenant so that we can run them for our tenants.

touch database/migrations/tenant mv database/migrations/*.php database/migrations/tenant

Now we run php artisan tenants:migrate:

php artisan tenants:migrate Tenant: e8002ec0-9a9e-11e9-8095-51e64ce28359 (tenant2.localhost) Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table Migrating: 2017_03_04_131126_create_posts_table Migrated: 2017_03_04_131126_create_posts_table Migrating: 2017_03_04_131334_create_categories_table Migrated: 2017_03_04_131334_create_categories_table Migrating: 2017_03_04_131558_create_tags_table Migrated: 2017_03_04_131558_create_tags_table Migrating: 2017_03_04_131702_create_post_tag_table Migrated: 2017_03_04_131702_create_post_tag_table Migrating: 2017_03_04_131909_create_comments_table Migrated: 2017_03_04_131909_create_comments_table Migrating: 2017_03_04_133429_add_columns_to_user Migrated: 2017_03_04_133429_add_columns_to_user Tenant: e5611150-9a9e-11e9-8315-b9eb127de2b8 (tenant1.localhost) Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table Migrating: 2017_03_04_131126_create_posts_table Migrated: 2017_03_04_131126_create_posts_table Migrating: 2017_03_04_131334_create_categories_table Migrated: 2017_03_04_131334_create_categories_table Migrating: 2017_03_04_131558_create_tags_table Migrated: 2017_03_04_131558_create_tags_table Migrating: 2017_03_04_131702_create_post_tag_table Migrated: 2017_03_04_131702_create_post_tag_table Migrating: 2017_03_04_131909_create_comments_table Migrated: 2017_03_04_131909_create_comments_table Migrating: 2017_03_04_133429_add_columns_to_user Migrated: 2017_03_04_133429_add_columns_to_user

Note: If you’re using the same sample blog repository as I chose, you might have to tweak the 2017_03_04_133429_add_columns_to_user migration (make the API key nullable). This is not an issue with the tenancy package but with the blog repository. I have submitted a PR so by the time you’re reading this, this should no longer be an issue.

Seeding the database

php artisan tenants:seed --class DummyDataSeeder Tenant: e8002ec0-9a9e-11e9-8095-51e64ce28359 (tenant2.localhost) Database seeding completed successfully. Tenant: e5611150-9a9e-11e9-8315-b9eb127de2b8 (tenant1.localhost) Database seeding completed successfully.

Visiting the sites

I didn’t want to set up Apache or Nginx just for a simple demonstration, so I used:

php artisan serve

Now we can visit the sites. If we visit tenant1.localhost:8000 and tenant2.localhost:8000, we can see two same applications, running from the same source code, but with different content.

First blog

First site

Second blog

Second site

Closing

My package makes it very easy to implement multi-tenancy into any Laravel application in a way that the application code doesn’t have to be aware of any tenancy. The magic is automatically identifying tenants based on the subdomain when a route in the routes/tenant.php file is visited (those routes have the InitializeTenancy middleware applied on them) and then accordingly switching the database and Redis connections and making some changes to the cache and filesystem.

If you’re building a multi-tenant app, I highly recommend using this package. Implementing tenancy on your own can be painful, which is why I created this package — any Laravel application I make can be made multi-tenant in 5 minutes using this package.

Thanks for reading. If you have any questions or suggestions about the package, feel free to open Issues on the GitHub page.

via Laravel News Links
How to make any Laravel application multi-tenant in 5 minutes