Laravel's default setup assumes one database, but the config supports any number of named connections simultaneously — genuinely useful for reading from a legacy system's database, connecting to a separate reporting/analytics warehouse, or splitting reads across a replica while writes go to a primary.
1. Configuring a second connection
// config/database.php
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST'),
'database' => env('DB_DATABASE'),
// ... the default connection
],
'legacy' => [
'driver' => 'mysql',
'host' => env('LEGACY_DB_HOST'),
'database' => env('LEGACY_DB_DATABASE'),
'username' => env('LEGACY_DB_USERNAME'),
'password' => env('LEGACY_DB_PASSWORD'),
],
],
// .env
LEGACY_DB_HOST=legacy-db.internal
LEGACY_DB_DATABASE=old_system
LEGACY_DB_USERNAME=readonly_user
LEGACY_DB_PASSWORD=...
2. Using a specific connection with the query builder directly
use Illuminate\Support\Facades\DB;
$legacyCustomers = DB::connection('legacy')->table('customers')->get();
3. Using a specific connection on an Eloquent model
class LegacyCustomer extends Model
{
protected $connection = 'legacy';
protected $table = 'customers';
}
$customers = LegacyCustomer::where('active', 1)->get();
A model with $connection set always queries that specific named connection, regardless of what the app's default connection is — this is the cleanest approach when a whole model consistently belongs to a separate database, rather than switching connections ad hoc per query.
4. Switching connection at query time instead
$customer = Customer::on('legacy')->find(1);
->on('legacy') is useful when the same model class occasionally needs to query a different connection situationally, rather than a model that's permanently tied to one non-default connection.
5. Cross-database queries don't work the way a normal JOIN does
A standard Eloquent relationship or a SQL JOIN assumes both tables live in the same database connection — querying across two genuinely separate database connections (not just separate schemas on the same server) can't be done with a single JOIN query. It typically requires querying each connection separately and combining the results in PHP, which is slower and more work than a same-connection join, and worth designing around rather than working with routinely if it can be avoided.
6. Common real use cases
- Legacy system integration — reading from an old system's database during a migration period, without merging its schema into the main app.
- Reporting/analytics database — a separate read-heavy warehouse, isolated from the primary transactional database so heavy reporting queries don't compete for resources with normal application traffic.
- Multi-tenant databases — a separate physical database per tenant, rather than a shared database with a tenant ID column, for tenants needing genuine data isolation.
7. Transactions don't span multiple connections
A DB::transaction() block only covers operations on the connection it's called against — writes to two different named connections inside what looks like one transaction aren't actually atomic together; a failure partway through can leave one connection's changes committed and the other's rolled back. This is a real constraint worth designing around when a genuine cross-database atomic operation seems necessary.