eg-mohamed / laravel-http-client-cache
Opt-in response caching for Laravel's HTTP client.
Package info
github.com/EG-Mohamed/laravel-http-client-cache
pkg:composer/eg-mohamed/laravel-http-client-cache
Fund package maintenance!
Requires
- php: ^8.4
- illuminate/contracts: ^11.0||^12.0||^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^11.0.0||^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
README
Opt-in response caching for Laravel's HTTP client. Cache outbound API responses with a single
fluent call — without replacing the Http facade or touching Laravel core.
use Illuminate\Support\Facades\Http; $response = Http::cache('products-api', 600) ->get('https://api.example.com/products'); $response->fromCache(); // false on the first call, true on subsequent calls
The cache layer wraps the normal PendingRequest, so everything you already use keeps working:
Http::fake(), headers, query params, JSON payloads, retries, throwing, and the full
Response API (body(), json(), status(), header(), successful(), …).
Installation
composer require eg-mohamed/laravel-http-client-cache
The service provider is auto-discovered. Publish the config file if you want to tweak defaults:
php artisan vendor:publish --tag="http-client-cache-config"
Basic usage
Pass a cache key and a TTL (in seconds) to Http::cache(), then chain any normal HTTP client
method:
$response = Http::cache('weather-today', 600) ->get('https://api.example.com/weather'); $response->fromCache(); // bool $response->json(); // works exactly like a normal response
By default only successful GET requests are cached. The first request hits the network;
matching requests within the TTL are served from cache without sending another request.
Flexible TTL (stale-while-revalidate)
Pass an array [$fresh, $stale] to use Laravel's Cache::flexible(). Values are served fresh
for $fresh seconds, then served stale (and refreshed in the background) up to $stale:
$response = Http::cache('currency-rates', [60, 300]) ->get('https://api.example.com/rates');
A plain integer, DateInterval, or DateTimeInterface uses normal Cache::remember() behavior.
Caching read-only POST APIs
Some APIs expose read-only data over POST (reports, search, GraphQL). Opt those methods in
explicitly — string, array, and enum values are all accepted and normalized to uppercase:
$response = Http::cache('erp-report', 600, methods: ['GET', 'POST']) ->post('https://legacy-erp.example.com/report', $payload);
Non-allowed methods simply bypass the cache and perform a normal request.
Checking fromCache()
Every response carries a fromCache() flag:
$response = Http::cache('products-api', 600)->get($url); if ($response->fromCache()) { // served from cache }
Cache store
Route the cached payload to a specific store:
Http::cache('github-releases', 600) ->cacheStore('redis') ->get('https://api.github.com/repos/laravel/framework/releases');
Tags
Tags are applied only when the underlying store supports them (e.g. Redis). With a store that does not support tagging, tags are silently ignored instead of throwing:
Http::cache('github-releases', 600) ->cacheStore('redis') ->cacheTags(['github', 'releases']) ->get($url);
Conditional caching
Decide per-response whether the result should be cached:
Http::cache('products-api', 600) ->cacheWhen(fn ($response) => $response->successful() && $response->json('available')) ->get($url);
Status-based caching
Limit caching to specific status codes:
Http::cache('products-api', 600) ->cacheStatuses([200, 201]) ->get($url);
Disabling cache
Skip the cache for a single request with dontCache():
Http::cache('products-api', 600) ->dontCache() ->get($url);
Or disable the package globally via config (enabled => false) — every request passes straight
through to the network.
Full fluent API
Http::cache('products-api', 600, methods: ['GET']) ->cacheStore('redis') ->cacheTags(['external-api', 'products']) ->cacheKeyPrefix('http-client') ->cacheWhen(fn ($response) => $response->successful()) ->cacheStatuses([200]) ->cacheMethods(['GET', 'POST']) ->get($url);
| Method | Purpose |
|---|---|
cache($key, $ttl = null, $methods = null) |
Start a cached request. |
cacheStore(?string $store) |
Choose the cache store. |
cacheTags(array|string $tags) |
Tag entries (tag-capable stores only). |
cacheKeyPrefix(?string $prefix) |
Override the cache key prefix. |
cacheWhen(Closure $callback) |
Cache only when the callback returns true. |
cacheStatuses(array|int|null $statuses) |
Cache only these status codes. |
cacheMethods(array|string|UnitEnum $methods) |
Override cacheable methods. |
dontCache() |
Bypass the cache for this request. |
fromCache() (on the response) |
Whether the response was served from cache. |
Configuration
return [ 'enabled' => true, // Master switch for the package 'default_store' => null, // Default cache store (null = app default) 'key_prefix' => 'http-client-cache', 'default_methods' => ['GET'], // Methods cached by default 'cache_successful_only' => true, // Only cache 2xx responses 'cache_statuses' => null, // Restrict caching to these statuses 'cache_failed' => false, // Allow caching failed responses 'respect_no_store' => false, // Skip caching when the response sends Cache-Control: no-store/no-cache 'default_ttl' => null, // Fallback TTL when none is provided 'tags' => [], // Default tags 'serialize_headers' => true, // Persist response headers ];
IDE autocompletion
Http::cache() and $response->fromCache() are registered as macros, so editors don't
autocomplete them out of the box. This package ships an ide-helper.php stub at its root that
PhpStorm indexes automatically from vendor/ — completion for Http::cache(...),
Factory/PendingRequest chaining, and Response::fromCache() works with no setup.
If you use barryvdh/laravel-ide-helper, the
macros are registered at boot and picked up automatically when you regenerate the helper:
php artisan ide-helper:generate
Testing notes
Http::cache() wraps the real pending request, so Http::fake() keeps working. In tests, use
the array cache store and remember that a cache hit does not send another request:
Http::fake(['*' => Http::response(['ok' => true], 200)]); $first = Http::cache('k', 600)->get('https://api.test/a'); $second = Http::cache('k', 600)->get('https://api.test/a'); expect($first->fromCache())->toBeFalse(); expect($second->fromCache())->toBeTrue(); Http::assertSentCount(1);
Behavior summary
- Only
GETis cached by default; opt in to other methods explicitly. - Only successful (2xx) responses are cached by default; failed responses are not.
- Method names are normalized to uppercase; string, array, and enum values are accepted.
- Array TTL uses
Cache::flexible(); everything else usesCache::remember(). - Only a small serializable payload (body, status, reason, headers) is stored — never the full
Responseobject. - Tags are honored only on stores that support them.
- A cache hit never sends a second HTTP request.
License
The MIT License (MIT). Please see License File for more information.
