Laravel multi-tenancy commands have arrived

Mohammed Osama
Feb 3 · 9 min read

Multi-tenancy concept has encountered the laravel community recently and made some noise due to the fact it’s going to make your life easier

— if you already know what tenancy is, just jump to the commands section.

imagine the following situation :
— We have an application that provides a company platform for each registered entity which may contain of projects,members etc..
You can just imagine how painful it’s to filter to get something through your database, and the preparation through your URL to get a specific thing of company, for example the URL can be like this

http://localhost:8080/api/company/company-name/project/project-name, and that’s just the beginning , imagine once the api development gets more complex, you will have to retrieve many things through url to get the thing you are seeking for, but in case of multi-tenant the URL can be like this

http://localhost:8080/api/project/project-name

  • you didn’t have to retrieve the company to just get the a specific project,
  • in this scenario you won’t have to check if the user owns this project or not and is the client member of this company to get this project.

weird huh ? Let’s dig into the benefits of multi-tenancy and the trade-off for this solution.

First things first what’s multi-tenancy ?

— Multi-Tenancy concept

A multi-tenant application is having the same codebase but it serves each client as if the application is just made for this tenant, so every single record you have inside your database belongs to this client, which means in essence that each tenant in some sense rented a database for his application which in our scenario his company.

— Multi-tenancy benefits

with that said, if each client of your application has a database that serves only him/her, this will make your code neater as you don’t have to filter or put much codebase just for the preparation as we have encountered earlier to just get a project of specific company, and for a security concern imagine having a problem with one of this clients somehow, the problem only affects the database belongs to this client, so you don’t lose rest of your data which belongs to the other clients.
In essence multi-tenancy helps you to have the following

  • get rid of the boring preparation while fetching data.
  • more security as each client has his/her own database.

Now, Let’s dig into the interesting part that you’re probably wondering about, how to manage multi-databases while laravel only provides one .env file

Well there is two types of multi-tenancy, you can handle the multi-tenancy with single database and with multi-database as each database serves a particular client in your application.

Multi-tenancy single database

we are still in our scenario, we try to retrieve projects of a company, and check if the authenticated user can really access this project, how can we achieve that, here where sessions,middleware and scopes come into the play.

Here what happens, a user logs in and creates a company / or list the companies he’s already member of, and once he hits any of this company, we get this company and set it to a session so we don’t have to bother ourselves anymore with which company we are dealing with, of course we won’t do this at each endpoint you have because this might be painful, but instead of that we will just create a middleware for that, once you hit a company we get this company we set the session with this company as the tenant of the application, that’s how the middleware should look like, if it’s not that clear yet.

<?phpnamespace 
App\Http\Middleware\Tenant;
use App\Company;
use App\Tenant\Manager;
use Closure;
class Tenant
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$tenant = $this->findTenant($request->company ?? session()->get('tenant'));
if (!auth()->user()->companies->contains('id', $tenant->id){
return redirect('/home');
}
$this->registerTenant($tenant);
return $next($request);
}
protected function registerTenant($tenant)
{
session()->put('tenant', $tenant->id);
}
protected function findTenant($id)
{
return Company::find($id);
}
}

on the first hit to a specific company the url will be like this :

http://localhost:8000/api/company/{company}
and by applying this middleware we can get the company through the url, retrieve it, store it into the session, so whenever we hit any other urls that contain a project that belongs to this company, we won’t get this company anymore as we already have it in our session.
the next thing is retrieving projects in such url
http://localhost:8000/api/project

here where the scopes come into the play.

we create a global scope for the projects model, so for each query that hits the projects table we only retrieve the projects that have the company_id , we already have from our session, here’s how a global scope may look like :

public function apply(Builder $builder) {  
return $builder->where('company_id', session('tenant'));
}

You can read about global scopes from docs

with this added inside your project model, you can just inside ProjectController, index method do just that

public function index() {  return response()->json(['projects' => Project::all() ]);
}

The query will look like this then :

select * from projects where company_id = 1 // if the tenant id is 1

with these simple steps, you relieved yourself from filtering, passing company everywhere inside your controllers and so on, you will just feel how powerful is that once your api gets complicated.

you can check on this repository for multi-tenancy single database

Multi-tenancy multi database

in the multi database scenario, you will have the regular database you used to have, but we will add a new table there which is the tenant connection.

on creating a new company we create a new tenant connection and this tenant connection represents the name of the database which you will connect the client to, and inside each created tenant you will have pretty much everything related to this company such as projects, members etc..

Let’s dig into the steps to achieve such a scenario.

On creating a new company, we create an observer or whatever you would like to do but I will just stick to Soc ( Separation of Concerns ) and do it in a separate file which is our observer.

class CompanyObserver {   public function creating (Company $company)   {
TenantConnection::create([
'company_id' => $company->id,
'database' => 'tenant_'. str_slug(Str::uuid())
]);
}
}

— TenantConnection is a model, and its table should contain of the company_id and the database name.

By having this observer, whenever the user creates the company, we create a database name, then we fire an event to create this database, and on accessing any company we change the database through the tenant connection.

at the first glance, this might seem to be simple, but once you want to invite the database commands that laravel provides to maintain your database , and switch the database connection for each tenant created, or on accessing a company, this might be painful therefore I decided to save your effort and create these commands for you.

Artify Tenant Commands

if you don’t know anything about aritfy, I recommend you to take a look at the other commands from here.

anyways! Let’s get started with fresh laravel project

laravel new Tenancy

head to your project directory and start installing the package

composer require sectheater/artify:dev-master --prefer-source

— If your laravel version is below 5.5, You have to register the service provider, otherwise that, The package is already discovered.

// in your config/app.php under the providers key
\Artify\Artify\ArtifyServiceProvider::class,

Let’s create our Company and Project Models associated with migrations.

php artisan make:model Company -m
php artisan make:model Project -m

companies table

— Don’t forget to set the relationship for the company-user.

projects table

— Don’t forget to update your .env file with the database credentials.

Let’s setup the package environment before migration.

Now, you will find a tenant directory created under the migrations directory, move the projects migration to it.

Now we can fire the migration command, everything will be migrated as normal except for the migrations under the tenant folder, that’s where artify will start the work.

Next, go to Company model and do the following :

And inside the projects model, add this Trait.

— You should add the ForTenant trait for each table you are going to have inside the multi-tenancy database, for example if you have members that you will have along with the projects inside the databases that artify will create, then attach this ForTenant trait.

The Last thing to do is setup the middleware that will check on accessing the multi-tenancy model which in our case the company model, and switch the database depending on the passed company.

Let’s register it inside your Http Kernel.

Believe it or not, we are done with the setup for multi-tenancy, now Let’s start testing this out.

First things first, Let’s start migrating our projects table.

Artify out of the box provides you with the normal migration commands you are used to but, they are made for the multi-tenancy , so we will be using them in a second, but first we have to create a company then create the database for this company then migrate the projects for this created database.

Don’t worry all you have to do here is just creating the company, and because of importing these traits, they will create you the tenant connection.

I’ll use the rweb to create one, you can create it using factories or whatever.

You should now see inside your database a created company, and a new tenant connection
the database name column inside tenant_connections will be named according to the APP_NAME you set inside your .env file.
in my case the database name now is set to laravel_1 ( it gets the id of the company and the application name and create the database name )

the tenant id is the company id in case you didn’t notice.

Now, Let’s use artify commands to migrate the projects table

php artisan tenants:migrate

this will check if the database exists or not for this tenant, and if not, it will create it then migrate the tables under the tenant directory.

And here we go, the database is created and the projects are migrated to there.

— Similarly, artify provides you the following commands.

  • tenants:migrate run migrations for tenants
  • tenants:migrate:fresh run fresh migrations for tenants
  • tenants:migrate:refresh Reset and re-run all migrations for tenants
  • tenants:migrate:reset run reset migrations for tenants
  • tenants:rollback Rollback migrations for tenants
  • tenants:seed Seeds tenant databases

Now, Let’s test our middleware, by creating the next route.

Let’s create a dummy project inside the laravel_1 database projects table.

then head to the route to see the following response.

it’s happening , we switched database and we are listing the projects.

— The middleware checks if the user is an owner of the passed company or not, if the user isn’t authorized , AuthenticationException is thrown.

— The middleware sets a session with the passed tenant id, which means that from now on you can just go to /project to index the projects and the company tenant will be retrieved from session, and then switch database like normal.

If you liked this post, please click the clap 👏button below a few times to show your support! Also, feel free to comment and give any kind of feedback. Don’t forget to follow me!

Mohammed Osama

Written by

Full stack developer & Security Researcher.