Let’s say, we have a web app, powered by and , that runs across different countries in South East Asia. Each venture will have their own users, products, categories etc. so to make it more manageable we want to have a separate database for each venture. But since we are using Postgres we can leverage schemas which I will describe shortly. Laravel Postgres I am using Laravel 5.5 and PostgreSQL 9.5 for my setup. You will find me using the word or interchangeably to represent the country. Tenant Venture First things first — Tenant identification The first thing we want to do in our multi-tenant web-app is to identify the venture/tenant from whom the request has been made. Create a file called inside folder and add a mapping for your ventures. The left-hand side is the domain name and the right-hand side is the schema for respective domain. ventures.php config Now, create a file called inside folder (or you can use any other folder you like; adjust namespace as required). This class will have a list of all the available ventures and helper methods to resolve venture from request URL & to load venture configs. Venture.php app/Library method in above piece of code resolves venture from the request URL. The first line of the method gets request URL without the protocol ( instead of ). Then we get all valid venture domains we added earlier to compare from. Finally, we return the database schema if it’s available, otherwise, we switch to the by default (or any other value that is defined in your ). resolveVenture example.com https://example.com public .env method loads or replaces configs that are venture specific. We will place all venture specific configs inside a folder called at the root level. method will check if the config file is available for given venture and loads it. We are using second parameter because we do not want to override global config repository object. If the second parameter is provided, we will set respective config object instead of the global one. This will help us to and venture specific configs later. loadVentureConfigs venture_configs loadVentureConfigs Repository $config get set Global config repository will always hold config from the current venture as per our design. Venture specific configurations Inside these files, we will use dot notation provided by Laravel to override config values. This is how the entries in your venture specific configuration files will look like: venture_configs/ph.php Note: You can also separate configs inside the same file using venture name as a prefix. I prefer to keep it separate. Config Loader Next thing we need is the config loader. When Laravel is loading configuration for the first time, we somehow need to tell it to load configuration for the current venture as well. We can do so by overriding class which is responsible to load configuration files. Illuminate\Foundation\Bootstrap\LoadConfiguration Create a file called inside directory and add following code. ConfigLoader.php app/Bootstrap Here we are extending class to override method. Notice Line #19 that loads venture specific configs by calling method we added earlier. Illuminate\Foundation\Bootstrap\LoadConfiguration loadConfigurationFiles loadVentureConfigs Confused how works here? Well, I just added an alias in to make it more readable. $app['venture'] bootstrap/app.php $app**-> ::class**, 'venture'); alias(\App\Library\Venture One last remaining thing is to tell Laravel to use our class instead of . We can do so by using . No, not really! If you dive deep into Laravel’s code you will notice that configs are loaded prior to the service providers. ConfigLoader Illuminate\Foundation\Bootstrap\LoadConfiguration ServiceProvider Illuminate\Foundation\Http\Kernel.php So, in order to override class, we need to use application bootstrapper ( ) which is loaded at the very beginning of application bootstrapping process. LoadConfiguration bootstrap/app.php Above statements appears just before statement in file. return $app; bootstrap/app.php Database Segregation Since we can now identify tenants and load configurations for the specific tenant, the only thing remaining is database segregation. There are different ways to segregate data for a multi-tenant application. We can either use different databases for each tenant, or use key-value pairs in every table to represent tenant, or, use different schemas. Every method has their own pros and cons which I will not go through in this tutorial. Since we are using Postgres, we can leverage Postgres Schemas. Schemas are like folders and can hold tables, views, functions, sequences, and other relations. By leveraging schema, we will limit cost and complexity while still maintaining performance and data security. So, instead of creating multiple databases, we can create one database and different schema for all ventures running the app. Create and register it in . VentureServiceProvider config/app.php In method, we will identify the venture and create database instance as necessary. In the first line (Line #18) we are extending the database object to use custom database object we provide. The second line changes schema config to the venture we resolved based on URL. Finally, we create and return a new object which points to the schema of current venture. register pgsql DatabaseManager db db DatabaseManager alias points to the _Illuminate\Database\DatabaseManager_ object. So overriding will override as well. At this point when you use database object with or façade or any other method provided by Laravel, you will always get the connection for current tenant or venture. app('db') DB:: Venture Specific Databases and Configs So, how can we access venture specific database schemas? Say, we need to perform some operation in schema while we are connected to schema? or we need to run some job using the command line? th vn In , we can create a database object for all schemas and push it into a service container. We can do the same for configs as well. VentureServiceProvider You might think that in above implementation we should have changed the configuration of venture also while switching the database after Line #9 instead of setting it in a new key. For instance, Now when we do it will switch to config of a respective venture as well. But NO, we must not do this as it will change the state of your application without even realizing which can lead to errors. Moreover, we need to reset config for ventures every time we do some database operation on another venture. app('db.th') The state of your app should be predictable at any given point of execution. Continuing on our first implementation, we can get global or venture specific db and/or configurations as described in the following snippet. Note that at this point we can only use helper or provided by Laravel to get configs of current venture. config() app('config') This keeps our app in more predictable state. Testing our multi-tenant application I have a database in place with schema for all 6 ventures. I created a table named with a column inside that table for all ventures. The path column will have a value of venture representing the country. For eg, schema will have value in column. search_path path th Thailand path routes/web.php Here, we are using Laravel’s default file as a view. The only thing changed is we have added value for the venture that we fetched in our route. And added the timezone config for respective venture. welcome.blade.php welcome.blade.php Let’s open which is our local domain for venture. We should get proper venture name from the database and proper timezone from the config. mt.local.th Thailand mt.local.th Now, let’s try with another domain which is our local domain for the . Notice the venture name and timezone. mt.local.ph Philippines mt.local.ph That’s it! You have successfully created a multi-tenant app using Laravel and PostgreSQL. It may not do a whole lot yet, but it creates a strong foundation for your multi-tenant application. Happy Coding! In the , we will see how we can extend Laravel’s migration implementation to gracefully handle migration for all schemas of our multi-tenant app. next part