Building a multi-tenant SaaS (Software as a Service) application with Laravel involves creating a structure where multiple clients (tenants) can use the same app instance, but each tenant’s data is isolated from others. Here’s a guide on how to set up a multi-tenant SaaS application in Laravel.
1. Define Your Multi-Tenancy Strategy
There are two primary strategies for multi-tenancy in SaaS applications:
- Single Database with Tenant Identification: Use a single database and differentiate tenants by a unique identifier (e.g., a
tenant_id
field in relevant tables). - Multiple Databases (Database per Tenant): Each tenant has its own database, which helps with data isolation and can improve performance and security.
Choose the strategy that fits your requirements. Laravel supports both, and there are libraries like tenancy/tenancy
or stancl/tenancy
that make multi-tenancy implementations easier.
2. Setting Up Your Project
If you haven’t already, create a new Laravel project:
laravel new my-saas-app
cd my-saas-app
3. Install a Multi-Tenancy Package
Using a package like stancl/tenancy
can simplify a lot of the boilerplate work required to build a multi-tenant app.
Install the package:
composer require stancl/tenancy
4. Configure the Multi-Tenancy Package
After installing, follow the package’s installation instructions to publish the necessary configurations and migration files. For example:
php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider"
This command publishes the config file config/tenancy.php
, where you can customize options like the database connection, central domain, and tenant-specific settings.
5. Implement Tenant Identification
Tenancy packages generally provide a middleware that can be added to your app routes to identify tenants based on subdomains or domains.
For example, if you want each tenant to access the app through a subdomain (e.g., tenant1.yourapp.com
), you can set up subdomain routing and add the tenancy middleware:
// routes/tenant.php
Route::middleware(['tenancy'])->group(function () {
Route::get('/', function () {
return view('dashboard');
});
});
Define tenant-specific routes in this file, which will load based on the identified tenant.
6. Configure Data Isolation (Single vs. Multiple Databases)
In the tenancy.php
configuration file, define how you want to handle tenant data. For example:
- Single Database: You can set tenant identifiers in your models. For example:
// app/Models/SomeModel.php
public function scopeForTenant($query, $tenantId)
{
return $query->where('tenant_id', $tenantId);
}
- Multiple Databases: Configure each tenant to use a separate database connection dynamically. Packages like
stancl/tenancy
handle this by creating a database per tenant and switching the database connection based on the identified tenant.
7. Create Tenant Models and Migrations
When using the single database approach, add a tenant_id
field to relevant tables. When using the multiple database approach, set up tenant migrations:
php artisan make:migration create_posts_table --path=database/migrations/tenant
Run tenant migrations with:
php artisan tenants:migrate
8. Build Tenant-Specific Features
Your application can include tenant-specific configurations, such as:
- Storage: Isolate storage per tenant by using the tenant’s ID as a directory identifier.
- Event Handling: Use events to set up tenant-specific features, such as creating default settings, roles, or resources upon tenant creation.
9. Set Up Centralized Management
Centralized management enables you to administer tenants from a single dashboard:
- Admin Routes: Create a separate
routes/admin.php
file for routes that only admins can access. - Admin Models: Use models to manage tenant creation, updates, and deletion.
- Authentication: Use a centralized authentication system, allowing admin users to access tenant data while tenants access only their data.
10. Testing & Security
Testing and security are critical for multi-tenant apps:
- Isolation: Ensure data is isolated correctly between tenants.
- Authorization: Use Laravel’s policies and guards to control access to resources.
- Thorough Testing: Test with multiple tenants to ensure that each tenant’s data is correctly separated.
11. Deployment Considerations
When deploying a multi-tenant SaaS app:
- Environment Configuration: Configure environment variables to handle multi-tenant logic.
- Database Scalability: Consider scaling database instances or using sharding strategies for larger numbers of tenants.
- Backup Strategy: Plan for data backup and restoration for each tenant, especially if using a multi-database setup.
Additional Tools and Libraries
- Laravel Passport or Laravel Sanctum for API-based authentication.
- Laravel Horizon for managing queues, which can be helpful for tenant-specific tasks (e.g., data processing jobs).
- Billing: Consider using a package like Laravel Cashier to integrate with Stripe for subscription billing.
Real-World Example: Team Collaboration Platform with Multi-Tenancy
Goal:
We’ll build a basic structure for a multi-tenant team collaboration app that supports the following functionality:
- Each company (tenant) has its own subdomain (e.g.,
company1.myapp.com
,company2.myapp.com
). - Each company can manage its own projects, tasks, and team members.
- Data is isolated so that one company’s data is not accessible to others.
We’ll use stancl/tenancy
for multi-tenancy support and Laravel Echo and Pusher for real-time notifications.
Step 1: Set Up the Project
Create a new Laravel project and install the stancl/tenancy
package to manage multi-tenancy:
laravel new team-collaboration
cd team-collaboration
composer require stancl/tenancy
After installing stancl/tenancy
, publish the package configuration files:
php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider"
Step 2: Configure Tenancy
- In
config/tenancy.php
, set the multi-tenancy configuration. - Decide whether you want single or multiple databases. For this example, let’s use multiple databases to isolate each company’s data fully.
Enable subdomain identification by updating tenancy.php
:
// config/tenancy.php
'tenant_identification' => 'domains', // Use domains or subdomains for tenant identification.
Step 3: Create Tenant and Centralized Models
Tenant Model Setup
Define models that will be tenant-specific, such as Project
and Task
. Use Laravel’s migration commands to create tenant-specific tables.
- Run the following command to create tenant-specific migrations:
php artisan make:migration create_projects_table --path=database/migrations/tenant
php artisan make:migration create_tasks_table --path=database/migrations/tenant
2. Define the migration schema for each table:
// database/migrations/tenant/create_projects_table.php
Schema::create('projects', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->timestamps();
});
// database/migrations/tenant/create_tasks_table.php
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->foreignId('project_id')->constrained()->onDelete('cascade');
$table->string('title');
$table->text('description')->nullable();
$table->enum('status', ['pending', 'in-progress', 'completed']);
$table->timestamps();
});
3. Run tenant migrations with:
php artisan tenants:migrate
Step 4: Implement Tenant Identification with Subdomains
In your routes file, use subdomains to identify tenants:
// routes/tenant.php
Route::middleware(['tenancy'])->group(function () {
Route::get('/', [DashboardController::class, 'index']);
Route::resource('projects', ProjectController::class);
Route::resource('tasks', TaskController::class);
});
Add this route file to RouteServiceProvider
so it loads automatically based on subdomain:
// app/Providers/RouteServiceProvider.php
Route::domain('{tenant}.' . config('app.url'))
->middleware('web')
->group(base_path('routes/tenant.php'));
Step 5: Implement Real-Time Notifications
Let’s use Pusher and Laravel Echo to broadcast real-time task updates. For instance, each team member will receive a notification when a new task is created in a project.
- Install Laravel Echo and Pusher:
composer require pusher/pusher-php-server
npm install --save laravel-echo pusher-js
2. Configure Pusher by adding your keys to the .env
file:
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=your-app-id
PUSHER_APP_KEY=your-app-key
PUSHER_APP_SECRET=your-app-secret
3. Create an event that will be broadcasted when a new task is created:
// app/Events/TaskCreated.php
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Queue\SerializesModels;
class TaskCreated implements ShouldBroadcastNow
{
use InteractsWithSockets, SerializesModels;
public $task;
public function __construct($task)
{
$this->task = $task;
}
public function broadcastOn()
{
return new PresenceChannel('project.' . $this->task->project_id);
}
}
4. Broadcast the event when a new task is created:
// app/Http/Controllers/TaskController.php
public function store(Request $request)
{
$task = Task::create($request->all());
broadcast(new TaskCreated($task));
return redirect()->back();
}
5. In the frontend, listen for real-time updates using Laravel Echo in a JavaScript file (like resources/js/app.js
):
import Echo from 'laravel-echo';
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
forceTLS: true
});
window.Echo.join(`project.${projectId}`)
.listen('TaskCreated', (event) => {
console.log('New task created:', event.task);
// Update the UI accordingly
});
Step 6: Testing the Multi-Tenant Setup
- Create Tenants: Use
tenants:create
to generate different tenants (companies).
php artisan tenants:create company1
php artisan tenants:create company2
- Access Tenant-Specific Dashboards: Access each tenant’s dashboard by visiting subdomains like
company1.myapp.com
andcompany2.myapp.com
. - Add Projects and Tasks: Test adding projects and tasks for each company. Observe that they’re isolated to each tenant and not shared across companies.
- Test Real-Time Notifications: Open two browser windows with the same tenant’s subdomain. When one user adds a new task, other users logged into the same tenant receive real-time updates through Laravel Echo.
Final Thoughts
With this setup, you have a scalable, real-time multi-tenant application that uses Laravel’s capabilities and the stancl/tenancy
package for effective tenant management. Each tenant can operate independently, with data isolation ensuring that each company only sees its own data.
Comments