API: Caching
HTTP caching with the ApiCache middleware: ETags, Cache-Control, and per-stream opt-out.
Streams\Api\Http\Middleware\ApiCache adds HTTP caching to API responses: an ETag, public Cache-Control headers, and 304 Not Modified for clients that already have the current version. It is not applied by default. Add it to the interfaces that should use it.
Separately, ApiResponse adds a Last-Modified header on cacheable (GET/HEAD) requests when the response data has a lastModified() method.
Why
Stream definitions and most entry lists change far less often than they are read. ETags let clients and CDNs skip downloading unchanged data, and the optional server-side cache saves re-running criteria for clients that ask for it.
Enable it
use Streams\Api\ApiInterface;
use Streams\Api\Http\Middleware\ApiCache;
use Streams\Api\Resources\EntriesResource;
use Streams\Api\Support\Facades\API;
API::interface(
ApiInterface::make('api')
->path('api')
->middleware([ApiCache::class])
->resources([EntriesResource::class])
);
How it behaves
For each request, the middleware:
- Skips non-cacheable methods (anything but GET and HEAD), requests with
Cache-Control: no-cacheorPragma: no-cache, routes with no{stream}parameter (such asGET /api/streams), and streams withconfig.cache.enabledset tofalse. - If the request sends
Cache-Control: max-age=N, caches the whole response server-side forNseconds in the stream's cache store, keyed on URL, method, body, and input. The response is returned without ETag headers. - Otherwise, runs the request and sets:
ETagto a quoted MD5 of the response bodyCache-Control: public, max-age=TTL, s-maxage=TTL, where TTL is the stream'sconfig.cache.ttl(default 3600 seconds)
- If the request's
If-None-Matchmatches the ETag, responds304 Not Modifiedwith no body.
Configure per stream
The middleware reads the same cache block as Core's query cache:
{
"config": {
"cache": {
"enabled": true,
"ttl": 600,
"store": "redis"
}
}
}
| Key | Effect on ApiCache |
|---|---|
enabled |
false turns HTTP caching off for this stream. Any other value (including unset) leaves it on. |
ttl |
max-age and s-maxage in seconds. Default 3600. |
store |
Laravel cache store for the max-age server-side cache. Default is your app's default store. |
Things to know
- Responses are marked
public. Don't putApiCacheon interfaces that return per-user data behind shared caches or CDNs unless you also vary the cache key (for example with aVary: Authorizationheader in your own middleware). - The ETag is computed after the response is built, so a 304 saves bandwidth, not server work.
- The JavaScript client exports an
ETagMiddleware, but in 3.0.0 it is a placeholder and does not sendIf-None-Matchyet.