Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Http Cache Laravel Package

slim/http-cache

A PSR-15 middleware that adds HTTP caching to Slim 4 apps, handling ETag/Last-Modified conditional requests, Cache-Control headers, and filesystem response caching to cut bandwidth and speed up repeat visits.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer (there is no Laravel service provider auto-discovery — this is a PSR-7 middleware package):

composer require slim/http-cache

Where to look first

  • src/Cache.php — the middleware itself and the fluent helpers (withEtag(), withLastModified(), withCacheControl(), withExpires()). Read this file end-to-end; it is small and is the real API surface.
  • src/CacheProvider.php — a trait that lazy-instantiates Cache and proxies the with* methods onto $this.
  • The README on GitHub is short and accurate for the two usage modes (global middleware vs. per-route helpers).

Important compatibility check before you commit: the middleware uses the Slim 3 signature __invoke(RequestInterface $request, ResponseInterface $response, callable $next). If your service is on Slim 4 (PSR-15 process()), this class will not drop into the middleware stack as-is — you either need a small adapter or to call the with* helpers directly from your handlers. In a Laravel app this package is not a drop-in: Laravel's HTTP kernel does not speak PSR-7 natively, so treat it as something you use in Slim microservices/sidecars that sit next to a Laravel app, or as a reference implementation for a Laravel CacheHeaders middleware.

First use case: a read-only JSON API where you want ETag + Cache-Control on every response:

$app = new \Slim\App();
$app->add(new \Slim\HttpCache\Cache('public', 3600)); // Cache-Control: public, max-age=3600

That single line is the 80% case: it adds Cache-Control, Expires, and (if configured) validator headers to whatever response the route produced, and answers conditional requests with 304 Not Modified.

Implementation Patterns

Pattern 1 — blanket headers for a whole API

$app->add(new \Slim\HttpCache\Cache('public', 86400));

Good for documentation endpoints and truly static lists. Bad for anything user-specific; prefer scoping it to a route group.

Pattern 2 — per-route validators via the CacheProvider trait

This is where the package earns its keep day-to-day. The trait gives you the with* helpers on a controller/handler:

use Slim\HttpCache\CacheProvider;

class ArticleController
{
    use CacheProvider;

    public function show($request, $response, $args)
    {
        $article = $this->articles->find($args['id']);

        $etag = md5($article->id . ':' . $article->updated_at->timestamp);

        $response = $this->withEtag($response, $etag);              // strong ETag
        $response = $this->withLastModified(
            $response,
            $article->updated_at->getTimestamp()
        );
        $response = $this->withCacheControl($response, 'public, max-age=60');

        return $response->withJson($article);
    }
}

The middleware then performs the comparison: an incoming If-None-Match matching your ETag, or If-Modified-Since >= your Last-Modified, results in a 304 with an empty body.

Pattern 3 — weak ETags for content-negotiated responses

Useful when the body differs by encoding but the underlying resource is the same:

$response = $this->withEtag($response, $etag, 'weak'); // emits W/"..."

Pattern 4 — private/no-store for authenticated resources

$response = $this->withCacheControl($response, 'private, no-store, max-age=0');
$response = $response->withHeader('Vary', 'Authorization');

The package does not add Vary for you. If your response varies by Accept, Accept-Encoding, or auth, you must add it yourself, otherwise downstream proxies will poison the cache.

Pattern 5 — mixing middleware and per-route helpers

Put new Cache(...) at the outermost layer (added last) so it sees the final headers your handler set. Keep validator computation inside the handler (withEtag / withLastModified) and let the middleware own the 304 decision. Do not have the middleware set an ETag and the route set one — you'll get a duplicated or overwritten header.

Integration tip for Laravel teams: if you run a Laravel app plus a Slim gateway (or are migrating), the cheapest integration is to have the Slim layer handle ETag/Last-Modified/Cache-Control and let Laravel's own Cache-Control middleware handle app responses. If you want the same behavior inside Laravel, port the ~150 lines of Cache::__invoke logic into an Illuminate\Http\Middleware style class — the validation rules (RFC 7232) are the valuable part, not the Slim plumbing.

Gotchas and Tips

  • Middleware order is the #1 bug source. Add the cache middleware last so it wraps the final response. If it runs before your controller, headers are missing and you never get a 304.

  • PSR-7 immutability. withEtag() returns a new response. $this->withEtag($response, $etag); without reassigning is a silent no-op. Always $response = $this->withEtag(...).

  • 304 bodies. A 304 must not carry a body. Verify the middleware replaces the stream with an empty one; if you're on a Slim 4 adapter, this is exactly the edge case that gets missed. curl -i a conditional request and confirm Content-Length: 0 or no body.

  • ETag quoting. The helper emits quoted ETags per RFC ("abc"). If you paste a pre-quoted value you'll produce ""abc"" and clients will never match. Pass the raw hash.

  • Last-Modified has one-second granularity. Two updates in the same second look identical to caches. If you have high write rates, rely on ETag for correctness and treat Last-Modified as advisory.

  • Expires vs Cache-Control. When both are present, max-age wins in every modern client. Don't be surprised when tweaking Expires appears to do nothing — change the max-age argument.

  • no-cache ≠ no-store. no-cache still stores and revalidates. For sensitive payloads use no-store, private.

  • Don't hash large bodies for ETags. md5($response->getBody()) forces full materialization and breaks streaming. Hash a row version, updated_at, or a content digest computed at write time.

  • Debugging conditional requests:

    curl -i -H 'If-None-Match: "your-etag"' https://api.test/articles/1
    curl -i -H 'If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT' https://api.test/articles/1
    

    A 200 where you expect 304 almost always means (a) the handler reassigned the response after setting headers, (b) a proxy stripped If-None-Match, or (c) the ETag changed between requests.

  • Extension points. Cache is a plain class — subclass it to inject Vary or standard security headers, or wrap it with your own middleware to log cache hit/miss decisions. The with* helpers are also usable directly outside the middleware if you only want to emit headers and handle 304s yourself.

  • Slim 4 / PSR-15. Write a thin adapter (process() → invoke the Slim-3-style callable) or skip the middleware and call the with* methods from handlers. Don't assume composer require makes it work in a Laravel or Slim 4 stack without that shim.

  • Pin the version. The package is small and low-traffic (116 stars, zero dependents); pin a known-good tag in composer.json and cover the 304 path with a functional test so a future minor bump can't silently change header semantics.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings