A cache stores the answer to an expensive question for a while, so the next request can reuse it instead of working it out again.
📝It's the sticky note on your fridge with the pizza place's number. Looking it up online every time works, but the note is instant. You just need to update the note when the number changes.
Do the hard work once
Faster pages
Reading a cached value takes around a millisecond. A heavy report query can take hundreds.
Less database load
A popular sidebar shown on every page can hit the database once an hour instead of once per visitor.
Fewer slow API calls
Exchange rates or weather from an outside API can be reused for minutes, saving time and rate limits.
Cache hit vs cache miss
posts.popular- Key
- The name a value is stored under, like
posts.popular. Same key, same value. - TTL
- “Time to live”: how long the value stays before it expires, in seconds or as a date.
- Invalidation
- Removing a cached value early because the underlying data changed.
- Driver / store
- Where the cache physically lives: database, Redis, files, memory, etc.
Cache like a pro
-
Pick a cache driver
The driver is set with
CACHE_STOREin.env. New Laravel 11+ apps default todatabase(the migration for itscachetable is included).CACHE_STORE=databaseDriver Good for Notes databaseGetting started, small apps No extra software; shared across servers redisProduction, high traffic Very fast in-memory store; also great for queues memcachedProduction Fast in-memory, simpler than Redis fileSingle-server apps Stored in storage/framework/cache; not shared between serversarrayTests Lives only for the current request -
Use
Cache::rememberThis is the method you'll use 90% of the time. It returns the cached value if it exists; otherwise it runs your closure, stores the result for the TTL, and returns it.
use Illuminate\Support\Facades\Cache; $popular = Cache::remember('posts.popular', now()->addMinutes(10), function () { return Post::with('author') ->withCount('comments') ->orderByDesc('comments_count') ->take(5) ->get(); });For things that rarely change,
Cache::rememberForever('categories', fn () => Category::all())keeps the value until you remove it. -
The basic building blocks
rememberis built on a few simpler methods you can use directly.Cache::put('site.banner', 'Sale ends Friday!', 3600); // store for 3600 seconds Cache::get('site.banner', 'Welcome!'); // read, with a default Cache::has('site.banner'); // true / false Cache::forget('site.banner'); // delete one key Cache::increment('downloads'); // countersphp artisan cache:clearempties the whole application cache. -
Invalidate when the data changes
A cache that's never cleared shows stale data: a new post won't appear in “popular posts” for 10 minutes. The fix is to forget the key whenever the source data changes. Model events are a tidy place to do it.
protected static function booted(): void { $clear = fn () => Cache::forget('posts.popular'); static::saved($clear); static::deleted($clear); }Choose keys that include anything that changes the answer, for example
"user.{$user->id}.dashboard"or"posts.page.{$page}". Otherwise everyone sees the same cached result.ℹ️ The classic joke“There are only two hard things in computer science: cache invalidation and naming things.” Decide when each cached value should be cleared at the same time you write the
remembercall. -
Serve stale while refreshing with
Cache::flexibleWith
remember, the unlucky visitor who arrives just after expiry waits for the slow query.Cache::flexible(Laravel 11.23+) takes two times: a fresh period and a stale period.$stats = Cache::flexible('dashboard.stats', [300, 900], function () { return Stats::calculate(); // slow });- 0–5 minutes: the value is fresh and returned as-is.
- 5–15 minutes: the stale value is returned immediately, and Laravel recalculates it in the background after the response is sent.
- After 15 minutes: it's recalculated like a normal miss.
-
Group related keys with tags (Redis / Memcached)
Tags let you clear a whole family of keys at once. They're only supported by drivers like
redisandmemcached, notfileordatabase.Cache::tags(['posts'])->remember("posts.page.{$page}", 600, fn () => Post::paginate()); Cache::tags(['posts'])->flush(); // clears every page at once
Avoid this, do that
Cache::remember('dashboard', 3600,
fn () => auth()->user()->stats());
One key for every user: the first person's dashboard is shown to everyone.
Cache::remember("user.{$user->id}.dashboard", 3600,
fn () => $user->stats());
Include the user ID (and any filters) in the key.
if (Cache::has('x')) {
$x = Cache::get('x');
} else {
$x = slow();
Cache::put('x', $x, 600);
}
Verbose, and easy to get subtly wrong.
$x = Cache::remember('x', 600, fn () => slow());
One line that does the same thing correctly.
Don't cache everything “just in case”. Fix N+1 queries first (see Eager Loading), measure with Debugbar or Telescope, and cache only what's genuinely slow or heavily repeated.
- Cache the list of categories with
Cache::rememberForever('categories', ...)and show it in your layout. - Add a
saved/deletedhook onCategorythat forgets the key. Create a category and check that it appears. - Cache “popular posts” for 10 minutes and compare the query count with Debugbar on a first and second visit.
- Rewrite it with
Cache::flexibleusing[300, 900]. - Run
php artisan cache:clearand confirm the next request is a miss.
Key takeaways
Cache::remember(key, ttl, fn)returns the cached value or computes and stores it.- Set the driver with
CACHE_STORE:databaseto start,redisfor production at scale. - Clear stale data with
Cache::forgetwhen the source data changes (model events work well). - Put everything that changes the answer (user ID, page, filters) into the key.
Cache::flexibleserves stale values instantly while refreshing in the background.