Creating Multi-Tenant Applications with Laravel

Mohammed S. Yaghi
9 min readApr 20, 2023

--

Using the multi-tenancy design pattern, a single application can serve several tenants, each with specific needs and data. Applications that support numerous clients or organizations may find this to be quite advantageous.

In this article, we’ll look at how to create multi-tenant applications with Laravel, one of the most popular PHP web frameworks.

We’ll cover the following topics:

· Planning for multi-tenancy

· Implementing multi-tenancy with middleware

· Handling user authentication and authorization

· Scaling a multi-tenant application

By the end of this article, you’ll have a good understanding of how to create robust, secure, and scalable multi-tenant applications with Laravel.

Planning a Multi-Tenant Application

Before we begin implementing multi-tenancy in Laravel, it’s important to consider some key design decisions. These include:

1.Data Isolation:

The act of segregating tenant data so that one tenant’s data cannot be accessed by another tenant is known as data isolation. This can be accomplished by keeping the data belonging to each tenant in a different database or schema. Each tenant’s data might be kept in a separate database with a unique identity that maps to the tenant’s account, for instance, if you’re developing a SaaS application that aids firms in managing their inventories.

Another important aspect of data isolation is securing any shared data, such as reference tables, to ensure that tenants cannot modify or access each other’s data. For example, if your application includes a list of product categories that is shared across tenants, you may want to limit access to this data to administrators or add additional constraints to ensure that tenants cannot modify the data.

To achieve data isolation, you can store each tenant’s data in a separate database or schema. In Laravel, you can use a package such as Tenancy to handle this. Here’s an example of how to use Tenancy to create a new tenant and switch to their database:

use Hyn\Tenancy\Contracts\Repositories\WebsiteRepository;
use Hyn\Tenancy\Contracts\Repositories\HostnameRepository;
use Hyn\Tenancy\Models\Website;
use Hyn\Tenancy\Models\Hostname;

$website = new Website;
$website->uuid = 'unique-tenant-identifier';
app(WebsiteRepository::class)->create($website);

$hostname = new Hostname;
$hostname->fqdn = 'tenant.domain.com';
$hostname = app(HostnameRepository::class)->create($hostname);
app(HostnameRepository::class)->attach($hostname, $website);

tenancy()->init($website, $hostname);

This code creates a new tenant with a unique identifier and a hostname of “tenant.domain.com”. It then switches to the new tenant’s database, allowing you to perform database operations that only affect that tenant’s data.

2.Tenant Management:

Tenant management is another key consideration when building a multi-tenant application. It includes features for creating new tenants, suspending or deleting existing tenants, and managing tenant resources such as storage and bandwidth.

For example, if you’re building a multi-tenant e-commerce platform, you may need to provide tools for merchants to create their online store and manage their inventory, orders, and payments. Additionally, you may need to include features for administrators to manage tenants, such as creating new tenants, setting resource limits, and monitoring usage.

To implement tenant management features, you can create a dedicated controller that handles tasks such as creating new tenants, suspending or deleting existing tenants, and managing tenant resources.

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Tenant;
use Illuminate\Http\Request;

class TenantController extends Controller
{
public function index()
{
$tenants = Tenant::all();
return view('admin.tenants.index', compact('tenants'));
}

public function create()
{
return view('admin.tenants.create');
}

public function store(Request $request)
{
$tenant = new Tenant;
$tenant->name = $request->name;
$tenant->domain = $request->domain;
$tenant->save();
return redirect()->route('admin.tenants.index');
}

public function suspend(Tenant $tenant)
{
$tenant->suspended_at = now();
$tenant->save();
return redirect()->route('admin.tenants.index');
}

public function delete(Tenant $tenant)
{
$tenant->delete();
return redirect()->route('admin.tenants.index');
}
}

This code creates a TenantController with methods for displaying a list of tenants, creating a new tenant, suspending a tenant, and deleting a tenant. You can add additional methods as needed to handle other tenant management tasks.

3.Customization:

Customization is another important consideration when building a multi-tenant application. Depending on the nature of the application, tenants may require varying degrees of customization. For example, tenants may want to configure their own branding, payment gateway, or integration with external systems.

For example, if you’re building a SaaS platform for project management, you may want to provide tenants with the ability to customize their project workflows, add custom fields to tasks, and configure notifications based on their specific needs.

To implement customization features, you can create a settings page where tenants can configure their settings. You can store the settings in the tenant’s database and retrieve them as needed.

namespace App\Http\Controllers\Tenant;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class SettingsController extends Controller
{
public function index()
{
$settings = auth()->user()->settings;
return view('tenant.settings.index', compact('settings'));
}

public function update(Request $request)
{
$settings = auth()->user()->settings;
$settings->update($request->all());
return redirect()->route('tenant.settings.index');
}
}

This code creates a SettingsController with methods for displaying a settings page and updating the tenant’s settings. You can add additional methods as needed to handle other customization tasks.

4.Performance:

Performance is critical for multi-tenant applications, as a slow application can lead to a poor user experience and lost revenue. To ensure optimal performance, the application should be designed to scale horizontally across multiple servers or cloud instances.

For example, if you’re building a multi-tenant CRM platform, you may need to distribute tenant data across multiple servers to ensure that the application can handle high traffic and provide fast response times. Additionally, you may need to use caching and load balancing techniques to optimize performance.

To ensure optimal performance, you can implement caching and load balancing strategies. For example, you can use Laravel’s built-in caching mechanisms such as Redis or Memcached to cache frequently accessed data, or use a load balancer such as NGINX or HAProxy to distribute traffic evenly across multiple application instances.

Here’s an example of how to implement caching in a Laravel-based multi-tenant application using Redis:

namespace App\Services;

use Illuminate\Support\Facades\Cache;

class TenantService
{
public function getTenantData($tenantId)
{
$data = Cache::remember("tenant_data_$tenantId", 60, function () use ($tenantId) {
return Tenant::find($tenantId)->data;
});
return $data;
}
}

This code creates a TenantService with a method that retrieves a tenant’s data from the database, but caches the data for 60 seconds to avoid unnecessary database queries. You can adjust the cache duration as needed based on your application’s requirements.

5.Security:

Security is another critical consideration for multi-tenant applications. The application should have a robust security model that ensures the confidentiality, integrity, and availability of tenant data. Security considerations include data encryption, access control, auditing, and monitoring.

For example, if you’re building a multi-tenant financial management platform, you may need to encrypt sensitive data such as credit card numbers and bank account information. Additionally, you may need to implement access controls to ensure that only authorized users can access tenant data, and include auditing and monitoring tools to track user activity and detect security breaches.

To ensure the security of your multi-tenant application, you should implement robust authentication and authorization mechanisms. Laravel provides built-in authentication features such as the Auth facade and Laravel Passport for implementing OAuth2 authentication.

Here’s an example of how to implement authentication in a Laravel-based multi-tenant application using Laravel Passport:

Route::middleware('auth:api')->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
});

This code creates a route group that requires authentication using the auth:api middleware. You can use this route group to define API endpoints that require authentication before they can be accessed.

6.Monitoring and Reporting:

Monitoring and reporting tools are critical for multi-tenant applications, as they allow tenants to understand how they’re using the application and identify areas for optimization. For example, if you’re building a multi-tenant analytics platform, you may need to provide tenants with tools for monitoring their website traffic, conversion rates, and other key performance indicators.

Additionally, you may need to provide administrators with tools for monitoring resource usage, such as disk space and bandwidth, to ensure that tenants are not exceeding their resource limits.

In conclusion, building a multi-tenant application requires careful planning and consideration of several factors, including data isolation, tenant management, customization, performance, security, and monitoring and reporting. By addressing these considerations, you can ensure that your application meets the needs of its users and can scale to meet the demands of a growing user base.

Implementing Multi-Tenancy with Middleware

Once we have a plan for our multi-tenant application, we can begin implementing it in Laravel. The first step is to create middleware that will set up the correct database connection and credentials for each request.

We can create a TenantMiddleware class that will identify the current tenant and set up the correct database connection. Here’s an example:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\DB;

class TenantMiddleware
{
public function handle($request, Closure $next)
{
$tenant = $this->resolveTenant($request);

// Set the database connection for the current tenant
config(['database.connections.tenant' => [
'driver' => 'mysql',
'host' => $tenant->db_host,
'port' => $tenant->db_port,
'database' => $tenant->db_database,
'username' => $tenant->db_username,
'password' => $tenant->db_password,
]]);

DB::purge('tenant');
DB::setDefaultConnection('tenant');

// Store the current tenant in the request object
$request->merge(['tenant' => $tenant]);

return $next($request);
}

protected function resolveTenant($request)
{
// Resolve the current tenant based on the request
// This could be done using a subdomain, path prefix, or custom header
$tenantId = $request->header('X-Tenant-ID');

return Tenant::findOrFail($tenantId);
}
}

In this example, we’re using the resolveTenant method to identify the current tenant based on a custom HTTP header. We’re then using the config function to set up the database connection for the current tenant, and the DB facade to set the default database connection and purge any existing connections. Finally, we’re storing the current tenant in the request object using the merge method.

To use the middleware, we can add it to the application’s middleware stack. This can be done in the App\Http\Kernel class:

protected $middleware = [
// Other middleware...
\App\Http\Middleware\TenantMiddleware::class,
];

Once the middleware is in place, we can use it to implement tenant isolation in our controllers, models, and other components.

For example, in a controller that manages a tenant’s data, we can retrieve the current tenant from the request object and use it to query the database:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class TenantController extends Controller
{
public function index(Request $request)
{
$tenant = $request->input('tenant');
$users = User::where('tenant_id', $tenant->id)->get();

// ...
}

// ...
}

Handling User Authentication and Authorization

In a multi-tenant application, we need to ensure that users can only access data that belongs to their own tenant. Laravel provides a robust authentication and authorization system that can be extended to support multi-tenancy.

One approach is to use Laravel’s built-in Gate class to define policies that restrict access to data based on the user’s tenant. Here’s an example:

namespace App\Policies;

use App\User;
use App\Tenant;
use Illuminate\Auth\Access\HandlesAuthorization;

class TenantPolicy
{
use HandlesAuthorization;

public function view(User $user, Tenant $tenant)
{
return $user->tenant_id === $tenant->id;
}

// ...
}

In this example, we’re defining a policy that allows users to view a tenant only if they belong to the same tenant. We can then use this policy in our controllers and other components:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Tenant;

class TenantController extends Controller
{
public function index(Request $request)
{
$this->authorize('view', $request->input('tenant'));

$tenant = $request->input('tenant');
$users = User::where('tenant_id', $tenant->id)->get();

// ...
}

// ...
}

Scaling a Multi-Tenant Application

As our multi-tenant application grows, we may need to scale it to handle more tenants and more data. One approach is to use database sharding, which involves splitting data across multiple database instances based on a sharding key.

Laravel provides support for database sharding through the Illuminate\Database\Eloquent\Shardable trait. We can use this trait in our models to automatically route queries to the correct database based on the sharding key:

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Shardable;

class User extends Model
{
use Shardable;

protected $shardKey = 'tenant_id';

// ...
}

In this example, we’re using the Shardable trait and specifying the tenant_id column as the sharding key. Laravel will automatically route queries for each tenant to the correct database based on their tenant_id.

Conclusion

In this article, we’ve explored how to create multi-tenant applications with Laravel. We’ve covered key considerations for planning a multi-tenant application, how to implement multi-tenancy with middleware, how to handle user authentication and authorization, and how to scale a multi-tenant application. With these techniques, you can create powerful and flexible applications that can serve multiple customers or organizations with ease.

--

--