← Back to articles
Performance & Databases

Caching Is Not a Luxury, It's a Necessity. How and Why to Use It in Laravel

A practical guide to caching in Laravel: from caching expensive queries and full pages to smart invalidation with tags and atomic locks to prevent cache stampedes.

✦
Featured article ↗

Caching Is Not a Luxury, It's a Necessity. How and Why to Use It in Laravel

Introduction: A Slow Application Is Lost Users

How many times have you closed a website or app because it was "loading"? In the modern digital world, speed is not just a convenience; it's a user expectation and a key factor for SEO, conversion, and user retention.

One of the most effective ways to speed up your web application is caching. Simply put, caching is storing the results of expensive operations (database queries, complex calculations, API calls) in a temporary but fast storage (like RAM). When the same data is requested again, it's served from there, saving time and resources.

Today, I'll show you how simple and elegant caching can be in Laravel—a framework that turns this task from a chore into a pleasure.

1. Why Laravel? Because Caching Here Is a First-Class Citizen

Laravel provides a unified, elegant API for working with various cache systems right out of the box: Redis, Memcached, files, and even databases. You write the same code, and the driver can be changed with a single line in the config (config/cache.php). That's the power of abstraction!

// Cache driver configuration (e.g., in .env) CACHE_DRIVER=redis // or memcached, file, database

2. Practice: From Simple to Complex. Real-World Examples

Example 1: Caching Results of a Heavy Query

Imagine you need to display a list of popular products on the homepage. A query with JOINs and aggregates can be slow.

Without Cache:

public function index() { $products = Product::with('category', 'reviews') ->where('is_active', true) ->withCount('orders') ->orderBy('orders_count', 'desc') ->take(20) ->get(); // The query runs every single time! return view('home', compact('products')); }

With Cache (for 60 minutes):

use Illuminate\Support\Facades\Cache; public function index() { $products = Cache::remember('homepage.popular_products', 60, function () { return Product::with('category', 'reviews') ->where('is_active', true) ->withCount('orders') ->orderBy('orders_count', 'desc') ->take(20) ->get(); }); return view('home', compact('products')); }

What does this give? After the first query, the data is saved in Redis for an hour. 10,000 visitors? The database will receive just 1 query per hour instead of 10,000. Database load and response time are reduced dramatically.

Example 2: Caching an Entire Page (Page Caching)

If a page is static for all users (e.g., "Terms of Service"), you can cache the entire rendered output.

// In your routes (web.php) Route::get('/terms', function () { return Cache::rememberForever('page.terms', function () { return view('pages.terms')->render(); // Render once and store the HTML }); });

rememberForever() is perfect for this. Just remember to clear the cache when the content updates!

Example 3: "Smart" Cache Invalidation on Data Change (Tags & Model Events)

The trickiest part of caching is invalidating it at the right time. Laravel solves this with tags (supported by drivers like Redis) and model events.

Scenario: We cache a list of blog articles. The cache must update when:

  • A new article is added.
  • An article is updated or deleted.
  • The article's author changes (if that affects the display).
// In the Article model protected static function booted() { // On any save or delete, flush the cached collection static::saved(function () { Cache::forget('articles.all'); }); static::deleted(function () { Cache::forget('articles.all'); }); } // In the controller public function index() { $articles = Cache::remember('articles.all', 120, function () { return Article::with('author')->latest()->get(); }); return view('articles.index', compact('articles')); }

A more advanced way—using tags:

// Storing with tags $articles = Cache::tags(['articles', 'authors'])->remember('key', 3600, function () { return Article::with('author')->get(); }); // Flushing ALL data tagged 'articles' (e.g., when any article changes) Cache::tags(['articles'])->flush();

Example 4: Atomic Locks to Prevent Cache Stampedes

What if two users simultaneously request data missing from the cache, and both processes start the heavy operation? Laravel allows you to "lock" the operation.

use Illuminate\Support\Facades\Cache; public function getReportData($userId) { $lock = Cache::lock('report.lock.' . $userId, 10); // Lock for 10 seconds if ($lock->get()) { // Heavy report generation... $report = generateComplexReport($userId); Cache::put('report.' . $userId, $report, 300); $lock->release(); // Release the lock return $report; } // Wait for the other process to generate the report return Cache::get('report.' . $userId); }

3. Key Principles to Remember

  1. Cache "expensive" operations: Queries with GROUP BY, JOIN, N+1 problems, external API calls.
  2. Plan your invalidation strategy: Stale cache is often worse than no cache. Use model events, tags, or manual clearing via Artisan (php artisan cache:clear).
  3. Choose the right driver: Redis for production, file for local development.
  4. Don't cache everything: Personalized data (e.g., "My Profile") is harder to cache effectively. Start with the heaviest, most public parts of your application.

Conclusion

Caching in Laravel isn't magic; it's a powerful, well-designed tool available to every developer. Implementing it correctly can reduce server load by orders of magnitude and create a truly fast user experience.

Start with one method. Measure performance before and after. The results will surprise you!

Technologies & topics

Article tags

No articles match these filters.

Have a project or an idea to discuss?

Let's talk ↗