- Introduction
- Architecture Overview
- Multi-Tenancy Implementation
- Database Structure
- Authentication & Authorization
- Key Components
- Tenant Lifecycle
- Data Isolation
- Frontend Implementation
- Performance Considerations
- Security Measures
- Best Practices
A robust multi-tenant eCommerce platform built with Laravel and Vue.js using a multi-database tenancy architecture. This application provides strict data isolation between tenants (stores) by using separate databases for each tenant.
- Multi-Database Tenancy: Each store operates with its own dedicated database for maximum data isolation
- User Authentication: Tenant-specific user authentication and role-based authorization
- Product Management: Complete CRUD operations for products within each tenant store
- Shopping Cart: Fully-featured shopping cart system with session management
- Order Processing: End-to-end order management from checkout to fulfillment
- Responsive UI: Modern interface built with Vue.js and Inertia
- Backend: Laravel, PHP 8.x
- Frontend: Vue.js, Inertia.js
- Multi-Tenancy: stancl/tenancy package
- Styling: Tailwind CSS
- Database: MySQL (configurable)
- PHP 8.1+
- Composer
- Node.js & NPM
- MySQL
- Clone the repository
git clone https://github.com/iniakunhuda/laravel-multi-tenant.git
cd multi-tenant-ecommerce- Install PHP dependencies
composer install- Install JavaScript dependencies
npm install- Copy environment file and configure database settings
cp .env.example .env- Generate application key
php artisan key:generate- Run migrations for the central database
php artisan migrate- Create test tenants
php artisan tenants:create-test- Seed tenant databases (optional)
php artisan tenants:seed- Compile assets
npm run dev- Start the development server
php artisan serve- Running test (optional)
php artisan test| Tenant 1 (Electronics & Fashion) http://tenant1.localhost:8000 |
|
|---|---|
Homepage |
Admin Dashboard |
Admin Product List |
Admin Order Detail |
| Tenant 2 (Food & Grocery) http://tenant2.localhost:8000 |
|
Homepage |
|
Product Detail |
Shopping Cart |
Checkout Page |
Successful Checkout |
Each tenant store is accessible through its own domain:
- Tenant 1: http://tenant1.localhost:8000
- Tenant 2: http://tenant2.localhost:8000
Admin users can access the dashboard by logging in at: http://[tenant-domain]/login
Default admin credentials:
- Email: admin@example.com
- Password: password
The application follows a multi-database tenancy model where:
- A central database stores tenant information and domain mappings
- Each tenant has its own dedicated database containing store-specific data
- The application dynamically connects to the appropriate tenant database based on the domain request
- The central database with tenants and domains tables
- The tenant database schema showing all tables (users, products, categories, carts, orders, etc.)
- The relationships between entities with proper cardinality
- How the domain is resolved to identify the tenant
- The database connection switching process
- The complete flow from browser request to response
- How data isolation is maintained throughout the request
- Central Application: Manages tenant registration, creation, and administration
- Tenant Applications: Individual store instances with isolated data and business logic
- Tenant Resolver: Identifies the correct tenant and connects to its database
- Database Connection Manager: Handles dynamic database switching
The multi-tenancy implementation is based on the stancl/tenancy package, which provides:
- Domain-based tenant identification
- Dynamic database connection switching
- Tenant-aware routes and middleware
- Database creation and management tools
The Tenant model extends BaseTenant from the tenancy package and implements TenantWithDatabase:
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\Concerns\HasDatabase;
use Stancl\Tenancy\Database\Concerns\HasDomains;
class Tenant extends BaseTenant implements TenantWithDatabase
{
use HasFactory, HasDatabase, HasDomains;
protected $fillable = [
'id', 'name', 'email', 'is_active', 'data'
];
public static function getCustomColumns(): array
{
return [
'id',
'name',
'email',
'is_active',
'data',
];
}
}The TenancyServiceProvider configures the tenancy system and registers tenant-specific bootstrappers:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains;
class TenancyServiceProvider extends ServiceProvider
{
public function register()
{
// Register tenant bootstrappers and other configurations
}
public function boot()
{
// Configure tenant routes and middleware
}
}The central database contains:
- tenants: Stores tenant information (name, email, etc.)
- domains: Maps domains to tenants
Migration files:
2019_09_15_000010_create_tenants_table.php2019_09_15_000020_create_domains_table.php
Each tenant has its own isolated database with the following tables:
- users: Store administrators and customers
- categories: Product categories
- products: Store merchandise
- product_images: Images associated with products
- carts: Shopping carts for users or sessions
- cart_items: Items in shopping carts
- orders: Customer orders
- order_items: Items in orders
- sessions: User sessions
Tenant migrations are stored in the database/migrations/tenant/ directory.
Authentication is tenant-specific, with each tenant having its own users table. The system:
- Identifies the tenant based on the domain
- Connects to the tenant's database
- Authenticates against the tenant's users table
The User model includes a role field for authorization:
public function isAdmin()
{
return $this->role === 'admin';
}The application includes various controller types:
-
Central Controllers: Manage the central application functionality
HomeController: Handles central domain routing and tenant listing
-
Tenant Controllers: Handle tenant-specific functionality
Tenant\HomepageController: Tenant store homepageTenant\ProductController: Customer-facing product displayTenant\CartController: Shopping cart functionalityTenant\OrderController: Order processing
-
Admin Controllers: Manage tenant store administration
Tenant\Manage\ProductController: Product managementTenant\Manage\CategoryController: Category managementTenant\Manage\OrderController: Order managementTenant\Manage\CustomerController: Customer managementTenant\Manage\StoreStatsController: Store statistics
Key models include:
-
Central Models:
Tenant: Represents a tenant (store)
-
Tenant Models:
User: Store administrators and customersCategory: Product categoriesProduct: Store merchandiseProductImage: Product imagesCart: Shopping cartsCartItem: Items in cartsOrder: Customer ordersOrderItem: Items in orders
Custom middleware components:
TenantMiddleware: Ensures proper tenant contextHandleAppearance: Manages tenant-specific appearance settingsHandleInertiaRequests: Configures Inertia.js for the current tenant
Tenants can be created:
- Manually through the admin interface
- Programmatically using the
CreateTestTenantscommand
The tenant creation process:
- Creates a tenant record in the central database
- Creates a domain record associated with the tenant
- Creates a new database for the tenant
- Runs migrations on the tenant database
- Seeds initial data (optional)
When a new tenant database is created:
- All migrations in
database/migrations/tenant/are executed - The
TenantDatabaseSeederis run to populate initial data
The application includes custom commands:
CreateTestTenants: Creates test tenants for developmentSeedExistingTenants: Runs seeders on existing tenant databases
Each tenant has a completely separate database, providing:
- Strong data isolation
- Independent scaling
- Compliance with data residency requirements
- No risk of data leakage between tenants
Database connections are managed by:
- Identifying the tenant through domain resolution
- Dynamically switching the database connection
- Maintaining connection separation throughout the request lifecycle
The frontend uses:
- Vue.js components
- Inertia.js for server-client communication
- Tenant-specific page components
Tenant pages are located in resources/js/pages/tenant/ and include:
- Homepage
- Product detail
- Shopping cart
- Checkout and payment
- Order confirmation
The admin interface allows tenant administrators to:
- Manage products and categories
- Process orders
- View customer information
- Access store statistics
For production environments, implement connection pooling to:
- Reduce database connection overhead
- Improve request handling capacity
- Optimize resource utilization
Implement tenant-aware caching by:
- Namespacing cache keys with tenant identifiers
- Using database-specific cache stores when necessary
- Implementing cache invalidation strategies
Regularly validate tenant isolation by:
- Testing cross-tenant access attempts
- Verifying middleware is correctly applied
- Auditing database connection switching
All controllers implement:
- Strict input validation
- SQL injection prevention
- CSRF protection
Tenant user authentication includes:
- Secure password hashing
- Session management
- Role-based access control
When developing for this multi-tenant architecture:
-
Always be tenant-aware:
- Check if code will run in tenant context
- Use tenant-specific paths and URIs
-
Follow tenancy database conventions:
- Place tenant migrations in the tenant directory
- Use the correct database connection
-
Test with multiple tenants:
- Create test tenants with the provided commands
- Verify functionality across tenant boundaries
-
Optimize for scale:
- Consider connection limits as tenant count grows
- Monitor database performance per tenant
When adding new features:
- Determine if the feature is tenant-specific or central
- Place migrations in the appropriate directory
- Update tenant initialization processes if necessary
- Test with multiple tenants to verify isolation