Laravel for Beginners: Setting Up Your First Project
A complete first-day guide to Laravel — installation, folder structure, your first route, and your first migration.
Multi-tenancy means one Laravel application serving multiple distinct customers ("tenants") — each with their own data, and depending on the approach, sometimes their own subdomain or even their own database. Here are the three real approaches, and the trade-offs that actually matter when choosing between them.
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('tenant_id')->constrained();
});
class Post extends Model
{
protected static function booted()
{
static::addGlobalScope('tenant', function (Builder $query) {
if ($tenantId = app('current_tenant_id')) {
$query->where('tenant_id', $tenantId);
}
});
}
}
Trade-off: cheapest to build and operate — one database, one set of migrations. But a single missed tenant_id filter anywhere in the codebase is a real, serious data-leak risk between customers. The global scope above is a reasonable safety net, but query-builder methods that bypass Eloquent (raw DB::table() calls) won't automatically get it.
Each tenant gets their own PostgreSQL schema (or, on MySQL, database) within the same server, switched dynamically per-request based on the resolved tenant:
DB::statement("SET search_path TO tenant_{$tenantId}");
Trade-off: stronger data isolation than a shared table with a column, still one server to operate and back up. Migrations need to run once per schema, which adds real operational complexity as tenant count grows into the hundreds.
config(['database.connections.tenant' => [
'driver' => 'mysql',
'database' => 'tenant_' . $tenantId,
// ...
]]);
DB::setDefaultConnection('tenant');
Trade-off: the strongest possible isolation — a bug in one tenant's queries can never touch another tenant's data, since they're physically separate databases. Highest operational cost: migrations, backups, and connection pooling all now scale with tenant count, not with a fixed number of servers.
// Middleware, resolving tenant from subdomain
class IdentifyTenant
{
public function handle($request, Closure $next)
{
$subdomain = explode('.', $request->getHost())[0];
$tenant = Tenant::where('subdomain', $subdomain)->firstOrFail();
app()->instance('current_tenant', $tenant);
return $next($request);
}
}
Start with the shared-database, tenant_id approach unless you have a specific, known requirement (regulatory data residency, an enterprise customer demanding a dedicated database) that rules it out. It's dramatically cheaper to operate at low-to-medium tenant counts, and packages like spatie/laravel-multitenancy or stancl/tenancy handle the harder isolation approaches if you outgrow it later — migrating up is far easier than starting with unnecessary operational complexity.
A complete first-day guide to Laravel — installation, folder structure, your first route, and your first migration.
API routes, resources, Form Request validation, correct status codes, and Sanctum auth — a real, production-reasonable Laravel API.
One-to-many, many-to-many, polymorphic, and the N+1 query trap that catches almost every Laravel developer at least once.