Streamed responses and chunked downloads
Concept
Laravel supports conditional HTTP caching via ETags and Last-Modified dates. When implemented correctly, clients can avoid re-downloading unchanged resources by sending conditional request headers. The server returns 304 Not Modified if the resource hasn't changed.
response()->withEtag(string $etag) and response()->withLastModified(DateTime $date): These are the lower-level methods for adding ETag and Last-Modified headers.
$request->etag($content) vs Request::precondition(): Laravel provides a setEtag() method and the older Response::setEtag() approach.
The Illuminate\Http\Response's isNotModified() method: Checks whether the response is a 304 Not Modified based on the request's conditional headers.
Practical implementation with Cache-Control:
public, max-age=N— browser caches for N seconds without revalidation.public, no-cache— browser caches but must revalidate (sends If-None-Match or If-Modified-Since).private, no-cache— browser caches privately, must revalidate.no-store— no caching at all.
API resource caching pattern: Generate an ETag from the resource's updated_at timestamp and ID. If the client's If-None-Match matches, return 304. This saves bandwidth without sacrificing freshness guarantees.
Code Example
<?php
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
public function show(Request $request, User $user): JsonResponse|\Illuminate\Http\Response
{
// Generate ETag from resource's last-modified timestamp
$etag = md5($user->updated_at->timestamp . $user->id);
$lastModified = $user->updated_at;
// Build response
$response = response()->json(new UserResource($user));
$response->setEtag($etag);
$response->setLastModified($lastModified);
$response->setPublic();
$response->setMaxAge(300); // cache for 5 minutes
// Check if the client already has the latest version
// isNotModified() checks If-None-Match (ETag) and If-Modified-Since headers
if ($response->isNotModified($request)) {
return $response->setStatusCode(304); // 304 Not Modified — no body sent
}
return $response; // 200 with full response body
}
}
// List endpoint with collection ETag
public function index(Request $request): JsonResponse|\Illuminate\Http\Response
{
$users = User::all();
$etag = md5($users->max('updated_at') . $users->count());
$response = response()->json(['data' => $users]);
$response->setEtag($etag);
if ($response->isNotModified($request)) {
return $response->setStatusCode(304);
}
return $response;
}
// Cache-Control for static assets (handled at web server level, but useful to know)
// Images, fonts, CSS, JS — set long TTL + versioning
// 'Cache-Control: public, max-age=31536000, immutable' — for versioned assets (hashed filenames)
// 'Cache-Control: public, max-age=3600' — for assets without versioning
// API responses should generally not be publicly cached
// 'Cache-Control: private, no-cache' — user-specific data
// 'Cache-Control: public, max-age=60' — public data (product listings, etc.)