0

Streamed responses and chunked downloads

Advanced5 min read·lv-10-004
performance

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
<?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.)