Creating a Multi-Tenant Application with Laravel and Neon
Learn how to build a scalable multi-tenant application using Laravel and Neon's powerful database features
Multi-tenancy is a software architecture where a single instance of an application serves multiple tenants or clients.
Each tenant's data is isolated and remains invisible to other tenants. This approach is commonly used in Software as a Service (SaaS) applications. In this tutorial, we'll build the foundation for a multi-tenant SaaS application using Laravel and Neon.
By the end of this tutorial, you'll have a fully functional multi-tenant SaaS application where tenants can manage their own books, users, and settings, all while maintaining data isolation between tenants.
Prerequisites
Before we start, make sure you have the following:
- PHP 8.1 or higher installed on your system
- Composer for managing PHP dependencies
- Node.js and npm for managing front-end assets
- A Neon account for database hosting
- Basic knowledge of Laravel and Livewire
Setting up the Project
Let's start by creating a new Laravel project and setting up the necessary components.
Creating a New Laravel Project
Open your terminal and run the following command to create a new Laravel project:
Installing Required Packages
For our multi-tenant SaaS application, we'll use the following package:
stancl/tenancy
: A flexible multi-tenancy package for Laravel- Laravel Breeze: A minimal authentication starter kit for Laravel
Start by installing the stancl/tenancy
package:
After installing the package, let's set up the tenancy:
Register the TenancyServiceProvider
in the bootstrap/providers.php
file:
Let's install Laravel Breeze with the Blade views:
Next, install the required NPM packages:
Setting up the Database
Update your .env
file with your Neon database credentials:
After updating the .env
file, run the database migrations:
Implementing Multi-Tenancy
Now that we have our basic setup, let's implement multi-tenancy in our application.
Creating the Tenant Model
Create a Tenant
model:
Update the app/Models/Tenant.php
file:
This model extends the base Tenant
model provided by the tenancy package and implements the TenantWithDatabase
interface. We've also defined the fillable attributes and custom columns for our tenant.
The HasDatabase
and HasDomains
traits provided by the tenancy package allow us to manage tenant-specific databases and domains. This essentially means that each tenant will have its own database and domain providing data isolation between tenants.
To learn more about the tenancy package event system and how to customize the tenant model, refer to the stancl/tenancy documentation.
Configuring Tenancy
Update the config/tenancy.php
file to use our custom Tenant
model:
Also, update the central domains configuration:
Replace the default central domains with your own domain names.
This is an important part as this is how the tenancy package will determine which domain belongs to which tenant and load the tenant-specific data accordingly.
Feel free to review the other configuration options in the config/tenancy.php
file to customize the tenancy behavior based on your requirements.
Creating Tenant Migrations
The tenancy package has built-in event listeners that automatically run tenant-specific migrations when a tenant is created. For this we need to make sure that all of the tenant-specific migrations are in the database/migrations/tenant
directory.
As each tenant will have its own database, the migrations in the tenant directory will be used to create tenant-specific tables in the tenant's database.
Start by copying the default User migration to the database/migrations/tenant
directory:
This will be the base migration for tenant-specific tables.
Implementing Tenant Routes
The tenancy package provides middleware to handle tenant-specific routes. This allows you to define routes that are accessible only to tenants and not to central domains.
Start by creating a new file routes/tenant.php
for tenant-specific routes with the following content:
These routes will be loaded by the TenantRouteServiceProvider
and will be accessible only to tenants. The InitializeTenancyByDomain
middleware will set the current tenant based on the domain, and the PreventAccessFromCentralDomains
middleware will prevent access from central domains.
For more information on how to customize the tenancy routes, refer to the stancl/tenancy documentation.
Implementing Tenant Creation
Create a controller for tenant registration, this would usually be done by the admin users of the application:
Update the app/Http/Controllers/TenantController.php
controller and implement the tenant registration process:
This controller handles tenant registration, creates a new tenant in the database, and sets up the tenant's domain. The TenancyServiceProvider
will automatically map the tenancy events to the listener, which will create the tenant's database and run the tenant-specific migrations inside the database/migrations/tenant
directory for the new tenant.
In a nutshell, the controller has three methods:
showRegistrationForm()
: Displays the tenant registration formregister()
: Registers a new tenant, which creates a new tenant record and domainregistered()
: Displays a success message after registration
This controller will be used to manage tenant registration in our application. Allowing new tenants to register and create their own subdomain and database for their account.
Add routes for tenant registration in routes/web.php
:
Create the corresponding views for tenant registration starting by creating the resources/views/tenant/register.blade.php
file:
Then create the resources/views/tenant/registered.blade.php
file to display the success message after registration:
This completes the tenant registration process. Tenants can now register and create their own subdomain and database for their account. In a real-world scenario, you would protect the registration routes with authentication middleware to ensure that only authorized admin users can create new tenants.
Verifying Tenant Registration
To verify that the registration process works, visit http://laravel-multi-tenant-saas.test/register
and register a new tenant. After registration, you should see the success message with the tenant's domain.
Next go to your Neon dashboard and verify that the new tenant's database has been created:
You should see the newly created tenant in the tenants
table. You can also check the domains
table to verify that the tenant's domain has been added:
And to verify that you actually have a separate database for the new tenant, use the \l
command in the psql
console to list all databases or the following SQL query:
The tenant's database should be listed in the results and it should be named tenant{tenant_id}
.
The tenancy package allows you to configure the database naming convention for tenants. By default, the database name is
tenant{tenant_id}
where{tenant_id}
is the ID of the tenant. You can also configure the package to use separate schemas instead of separate databases for tenants.
With that done, you've successfully implemented tenant registration in your multi-tenant SaaS application. Next let's implement the tenant onboarding process.
Implementing Tenant Onboarding
Now that you can register new tenants, let's create an onboarding process.
Each tenant will need to create an account to access their dashboard. The domain will be used to identify the tenant, so we'll use the domain as the tenant's subdomain, e.g., tenant1.example.com
.
Create a new controller for tenant onboarding:
Update the app/Http/Controllers/Tenant/OnboardingController.php
to handle the onboarding process:
Add routes for the onboarding process in routes/tenant.php
inside the Route::middleware
group for tenant routes:
Create the onboarding view in resources/views/tenant/onboarding.blade.php
:
For simplicity, we're extending the Breeze guest layout for the onboarding form. But you can customize the layout to match your application's design and even have different layouts for the onboarding process based on each tenant's requirements.
To test the onboarding process, visit http://tenant1.example.com/onboarding
and complete the onboarding form. After submitting the form, you should be redirected to the tenant dashboard which we'll implement next.
Implementing Tenant Dashboard
Create a new controller for the tenant dashboard:
Update the app/Http/Controllers/Tenant/DashboardController.php
to display the tenant dashboard:
Create the dashboard view in resources/views/tenant/dashboard.blade.php
:
Add a route for the tenant dashboard in routes/tenant.php
inside the Route::middleware
group for tenant routes:
To test the tenant dashboard, visit http://tenant1.example.com/dashboard
after completing the onboarding process. You should see the dashboard view with a welcome message.
You can also check the users
table in the tenant's database to verify that the user account created during onboarding has been added:
This will show you the user account created during the onboarding process for that specific tenant in the tenant's database rather than the central database.
Conclusion
In this tutorial, we've built a simple multi-tenant application using Laravel and Neon. We've covered:
- Setting up the project and implementing multi-tenancy
- Creating a tenant registration process
- Implementing tenant onboarding
- Adding a tenant dashboard for individual tenants
This implementation provides a foundation for building more complex SaaS applications with Laravel and Neon. You can further expand on this system by:
- Adding more features to the tenant dashboard
- Implementing billing and subscription management
- Enhancing security with two-factor authentication
- Adding more tenant-specific customizations
Using the stancl/tenancy
package along with Neon, each tenant will have its own database. Thanks to Neon's autoscaling feature, you can easily scale your application as you onboard more tenants.
There are other packages and tools available to help you build multi-tenant applications with Laravel. You can explore these options based on your requirements and choose the one that best fits your needs. Some of the popular packages include: