Laravel Best Practices for Fast Data Retrieval
When it comes to speeding up data retrieval in Laravel, the biggest wins almost always come from how you query the database not from tweaking Laravel itself. Here's a practical, battle-tested approach to writing fast, efficient Eloquent and Query Builder code.
1. Select Only the Columns You Need
Avoid:
$users = User::all();Prefer:
$users = User::query()
->select(['id', 'name', 'email'])
->get();For a single record:
$user = User::query()
->select(['id', 'name', 'email'])
->find($id);2. Use exists() / count() Instead of Retrieving Data
Don't:
if (User::where('email', $email)->get()->isNotEmpty()) {
// ...
}Use:
if (User::where('email', $email)->exists()) {
// ...
}exists() is much cheaper because the database doesn't need to return the matching rows it just confirms they exist.
3. Use value() When You Need a Single Value
Instead of:
$user = User::select('name')->find($id);
$name = $user?->name;Use:
$name = User::whereKey($id)->value('name');4. Use pluck() for a Single Column
Instead of:
$users = User::select('id')->get();
$ids = $users->pluck('id');Use:
$ids = User::pluck('id');
// Or with a condition:
$emails = User::where('active', true)->pluck('email');5. Use Query Builder for Read-Heavy Queries
For simple reads where you don't need Eloquent models:
$users = DB::table('users')
->select('id', 'name', 'email')
->where('active', true)
->get();This avoids some of the Eloquent model hydration overhead. For large or read-heavy datasets, prefer:
Query Builder → DTO/array
over:
Eloquent → thousands of hydrated models
6. Index Your Database Properly
This is probably the most important optimization on this list.
If you frequently run:
User::where('email', $email)->first();Make sure email is indexed:
$table->string('email')->unique();For queries like:
Order::where('user_id', $userId)
->where('status', 'completed')
->get();Consider a composite index:
$table->index(['user_id', 'status']);Don't blindly index every column design indexes around your actual queries.
7. Avoid N+1 Queries
Bad:
$orders = Order::get();
foreach ($orders as $order) {
echo $order->user->name;
}This produces 1 query for orders + N queries for users.
Use eager loading instead:
$orders = Order::with('user')->get();Even better, restrict the relationship columns:
$orders = Order::with('user:id,name')->get();8. Don't Use with() Unnecessarily
This can become expensive:
User::with([
'orders',
'addresses',
'payments',
'roles',
])->get();Only load relationships that are actually required by the response.
9. Use cursor() / lazy() for Huge Datasets
If you're processing millions of records:
User::query()
->where('active', true)
->cursor()
->each(function (User $user) {
// process
});Or:
User::query()
->where('active', true)
->lazyById(1000)
->each(function (User $user) {
// process
});For processing large datasets, lazyById() is generally preferable to loading everything into memory.
10. Use chunkById() for Bulk Processing
User::query()
->where('active', true)
->chunkById(1000, function ($users) {
foreach ($users as $user) {
// process
}
});Prefer chunkById() over chunk() when records may be updated during processing, since it re-queries by ID and avoids skipping rows.
11. Cache Expensive or Repeated Reads
If data doesn't change frequently:
$settings = Cache::remember(
'settings',
now()->addHour(),
fn () => Setting::query()->pluck('value', 'key')
);For a frequently accessed lookup:
$user = Cache::remember(
"user:{$id}",
now()->addMinutes(10),
fn () => User::select('id', 'name', 'email')->find($id)
);But don't cache everything database indexes and good queries should come first.
12. Use EXPLAIN for Slow Queries
Given:
$orders = Order::query()
->where('customer_id', $customerId)
->where('status', 'pending')
->latest()
->get();Check the SQL query plan:
$query = Order::query()
->where('customer_id', $customerId)
->where('status', 'pending')
->latest();
dd($query->toRawSql());Then run the SQL with EXPLAIN ... to confirm the database is actually using your indexes.
My Practical Hierarchy
When optimizing reads in a Laravel application, I follow this order:
Correct database indexes
Efficient SQL/query conditions
Select only required columns
Avoid N+1 queries
Avoid unnecessary Eloquent hydration
Pagination / chunking for large datasets
Cache repeated expensive queries
Optimize PHP/Laravel code
For an API, a good pattern is:
$users = User::query()
->select(['id', 'name', 'email'])
->where('active', true)
->latest('id')
->paginate(50);rather than retrieving the entire table and filtering/processing it in PHP.
The Bottom Line
If you want fast Laravel retrieval: think "database first, Laravel second." A well-indexed database with a small, targeted query will almost always outperform attempts to optimize PHP code around an inefficient query.