Implementing multi-tenancy in Laravel with a separate database for each tenant is a complex task, but it can be achieved by following these key considerations and best practices:
-
Tenant Identification: Determine how you will identify tenants. This could be through subdomains, a specific URL segment, or a unique identifier in the request.
-
Database Connection Management: You need to dynamically switch the database connection based on the tenant. This can be done by modifying the database configuration at runtime.
-
Middleware for Tenant Resolution: Create a middleware to resolve the tenant from the request and set the appropriate database connection.
-
Environment Configuration: Ensure your
.envfile or configuration files are set up to handle multiple database connections. -
Migrations and Seeding: Automate the process of running migrations and seeders for each tenant's database.
-
Security: Ensure that data is isolated and secure between tenants. Validate that users can only access their own data.
-
Testing: Implement thorough testing to ensure that tenant isolation is maintained and that the application behaves correctly for each tenant.
Here's a basic example of how you might implement some of these steps:
Step 1: Middleware for Tenant Resolution
Create a middleware to resolve the tenant and set the database connection:
php artisan make:middleware TenantMiddleware
In TenantMiddleware.php:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
class TenantMiddleware
{
public function handle($request, Closure $next)
{
// Example: Resolve tenant from subdomain
$tenant = $this->resolveTenantFromRequest($request);
if ($tenant) {
// Set the database connection dynamically
Config::set('database.connections.tenant', [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'database' => $tenant->database_name,
'username' => $tenant->database_user,
'password' => $tenant->database_password,
]);
DB::purge('tenant');
DB::reconnect('tenant');
DB::setDefaultConnection('tenant');
}
return $next($request);
}
protected function resolveTenantFromRequest($request)
{
// Logic to resolve tenant, e.g., from subdomain
$subdomain = explode('.', $request->getHost())[0];
return \App\Models\Tenant::where('subdomain', $subdomain)->first();
}
}
Step 2: Register Middleware
Register the middleware in app/Http/Kernel.php:
protected $middlewareGroups = [
'web' => [
// Other middleware
\App\Http\Middleware\TenantMiddleware::class,
],
];
Step 3: Database Configuration
In config/database.php, add a tenant connection:
'connections' => [
// Other connections
'tenant' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'database' => '',
'username' => '',
'password' => '',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
],
Step 4: Migrations and Seeding
Create a command to run migrations and seeders for each tenant:
php artisan make:command MigrateTenants
In MigrateTenants.php:
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Tenant;
use Illuminate\Support\Facades\Artisan;
class MigrateTenants extends Command
{
protected $signature = 'tenants:migrate';
protected $description = 'Run migrations for all tenants';
public function handle()
{
Tenant::all()->each(function ($tenant) {
$this->info("Migrating tenant: {$tenant->name}");
// Set the database connection for the tenant
config(['database.connections.tenant.database' => $tenant->database_name]);
DB::purge('tenant');
DB::reconnect('tenant');
// Run migrations
Artisan::call('migrate', [
'--database' => 'tenant',
'--path' => '/database/migrations/tenant',
'--force' => true,
]);
});
}
}
By following these steps, you can set up a basic multi-tenancy system in Laravel with separate databases for each tenant. Remember to adapt the logic to fit your specific requirements and ensure robust error handling and security measures are in place.