Deep-dive into the middleware stack that hardens, accelerates, and instruments Laravel APIs while keeping response bodies untouched.
- 1. Design Goals
- 2. Stack Overview
- 3. Installation and Wiring
- 4. Middleware Deep Dive
- 5. Observability and Telemetry
- 6. Integration Patterns
- 7. Testing Strategy
- 8. Failure Scenarios and Mitigations
The API pipeline is built around four principles:
- Transport-only optimizations – payloads are compressed, cached, or wrapped in headers without mutating JSON structures.
- Deterministic behaviour – middleware order produces repeatable responses in multi-node clusters and under retries.
- First-class observability – latency, request identifiers, and cache status surface through HTTP headers compatible with APMs.
- Security by default – hardened response headers enforce modern browser and client behaviour.
| Middleware | Category | Primary Capability | Default Status |
|---|---|---|---|
ApiSecurityHeaders |
Hardening | Applies CSP, HSTS, referrer, and permissions headers | Enabled |
ApiResponseCache |
Caching | Serves GET requests from cache and invalidates on mutations | Disabled |
ApiETag |
Validation | Issues strong/weak ETags and honours conditional requests | Enabled |
ApiResponseCompression |
Transport | Negotiates Brotli/Gzip compression with thresholds | Enabled |
ApiPerformanceHeaders |
Telemetry | Emits timing, memory, query, and correlation headers | Enabled |
ApiCircuitBreaker |
Resilience | Applies rolling failure window and fallback status code | Optional |
ApiHealthCheck |
Diagnostics | Exposes /health probe with subsystem metrics |
Optional |
The recommended order inside the api group places hardening first, then caching/meta validators, followed by compression and finally telemetry.
-
Require and publish assets:
composer require vinkius-labs/laravel-page-speed php artisan vendor:publish --provider="VinkiusLabs\\LaravelPageSpeed\\ServiceProvider" -
Wire the stack using the configuration style for your Laravel version:
Laravel 10.x –
app/Http/Kernel.phpprotected $middlewareGroups = [ 'api' => [ // Core Laravel middleware … \VinkiusLabs\LaravelPageSpeed\Middleware\ApiSecurityHeaders::class, \VinkiusLabs\LaravelPageSpeed\Middleware\ApiResponseCache::class, \VinkiusLabs\LaravelPageSpeed\Middleware\ApiETag::class, \VinkiusLabs\LaravelPageSpeed\Middleware\ApiResponseCompression::class, \VinkiusLabs\LaravelPageSpeed\Middleware\ApiPerformanceHeaders::class, \VinkiusLabs\LaravelPageSpeed\Middleware\ApiCircuitBreaker::class, \VinkiusLabs\LaravelPageSpeed\Middleware\ApiHealthCheck::class, ], ];
Laravel 11.x / 12.x / 13.x –
bootstrap/app.phpExtend the same
->withMiddlewareclosure used for the web stack:$middleware->appendToGroup('api', [ \VinkiusLabs\LaravelPageSpeed\Middleware\ApiSecurityHeaders::class, \VinkiusLabs\LaravelPageSpeed\Middleware\ApiResponseCache::class, \VinkiusLabs\LaravelPageSpeed\Middleware\ApiETag::class, \VinkiusLabs\LaravelPageSpeed\Middleware\ApiResponseCompression::class, \VinkiusLabs\LaravelPageSpeed\Middleware\ApiPerformanceHeaders::class, \VinkiusLabs\LaravelPageSpeed\Middleware\ApiCircuitBreaker::class, \VinkiusLabs\LaravelPageSpeed\Middleware\ApiHealthCheck::class, ]);
-
Toggle features via
config/laravel-page-speed.phpor environment variables (seedocs/CONFIGURATION.md). Example.envblock:API_CACHE_ENABLED=true API_CACHE_DRIVER=redis API_CACHE_TTL=300 API_CACHE_DYNAMIC_TAGS=true API_MIN_COMPRESSION_SIZE=1024 API_SHOW_COMPRESSION_METRICS=false API_TRACK_QUERIES=true API_QUERY_THRESHOLD=20 API_SLOW_REQUEST_THRESHOLD=1000 API_ETAG_ALGORITHM=md5 API_ETAG_MAX_AGE=300
- Adds
Strict-Transport-Security,X-Content-Type-Options,X-Frame-Options,Content-Security-Policy, andPermissions-Policy. - All directives are configurable; defaults align with OWASP ASVS Level 2.
- Combine with Laravel's rate limiting and auth middleware for holistic protection.
- Transparent caching for idempotent verbs (
GET,HEAD) with dynamic tagging based on request path and optional user context. - Mutation verbs (
POST,PUT,PATCH,DELETE) evict matching cache entries using tag indexes—seedocs/API-CACHE.mdfor a full walkthrough. - Supports per-user segregation via
per_userandcache_authenticatedflags.
- Generates hashes using the configured algorithm (
md5,sha1,sha256). - Handles both strong and weak validators; uses
If-None-Matchfor conditional GETs and returns304without body when the payload is unchanged. - Plays well with Response Cache: when cache is hit, ETag is emitted from stored metadata.
- Negotiates compression based on
Accept-Encoding. Prefers Brotli (br), falls back to Gzip when unsupported. - Respects
min_compression_sizeto avoid inflating small payloads. skip_error_compressionprevents compressing 4xx/5xx bodies when troubleshooting.- Optional metrics header:
X-Compression-Ratio,X-Original-Size, andX-Compressed-Size.
- Wraps the request lifecycle in a high-resolution timer.
- Emits headers:
X-Response-Time: milliseconds with two decimal precision.X-Memory-Usage: memory delta.X-Query-Count: total database queries executed (requirestrack_queries).X-Request-ID: generated UUID v4 if not already set upstream.X-Performance-Warning: descriptive message when thresholds are exceeded.
- Ideal for structured logging and correlation within distributed traces.
- Maintains a sliding window of failures keyed by URL or route depending on
scope. - On threshold breach, short-circuits subsequent requests for
timeoutseconds and emits configurable fallback response. - Emits
X-Circuit-Breaker-Stateheader (closed,half-open,open).
- Exposes a JSON document summarizing database, cache, disk, memory, and queue health.
- Optional response caching (
cache_results) reduces probe load; default TTL is 10 seconds. - Use with Kubernetes readiness/liveness probes or ALB/Gateway health checks.
-
Headers: Performance, cache, and breaker headers are parseable by Datadog, New Relic, Elastic APM, and custom log shippers.
-
Metrics correlation: Pair
X-Request-IDwith structured logs (Log::withContext) to trace entire request lifecycles. -
APM tagging: Example (Laravel + OpenTelemetry):
app('telemetry')->currentSpan()?->setAttribute('http.api.cache_status', $response->headers->get('X-Cache-Status')); app('telemetry')->currentSpan()?->setAttribute('http.api.request_id', $response->headers->get('X-Request-ID'));
-
Dashboards: export CSV of headers via Istio Envoy filters or API Gateway access logs to visualize hit rates and latency distribution.
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\VinkiusLabs\LaravelPageSpeed\Middleware\ApiSecurityHeaders::class,
\VinkiusLabs\LaravelPageSpeed\Middleware\ApiResponseCache::class,
\VinkiusLabs\LaravelPageSpeed\Middleware\ApiETag::class,
\VinkiusLabs\LaravelPageSpeed\Middleware\ApiResponseCompression::class,
\VinkiusLabs\LaravelPageSpeed\Middleware\ApiPerformanceHeaders::class,
];Route::middleware(['throttle:120,1', 'bindings', 'page-speed.api'])->group(function () {
Route::get('/v1/products', ProductController::class);
Route::post('/v1/products', ProductStoreController::class);
});Enable expensive telemetry only outside production:
if (! app()->environment('production')) {
$router->pushMiddlewareToGroup('api', \VinkiusLabs\LaravelPageSpeed\Middleware\ApiPerformanceHeaders::class);
}- Unit tests: Validate header presence/values via PHPUnit’s
assertHeader. Target classes already have coverage undertests/Middleware. - Contract tests: Capture JSON schemas before enabling middleware to ensure payloads remain unmodified.
- Load testing: Run k6 or Locust with/without middleware to measure CPU, memory, and cache hit behaviour.
- Chaos drills: Force cache backend failures or simulate downstream outages to validate circuit breaker fallbacks.
| Scenario | Detection signal | Recommended action |
|---|---|---|
| ETag mismatch due to proxy modifications | Client never receives 304 responses | Ensure upstream proxies preserve ETag headers. |
| Cache poisoning by per-user data | Users receive responses with foreign state | Enable per_user cache segmentation. |
| Slow compression on large payloads | Increased X-Response-Time but low CPU idle |
Raise min_compression_size or enable Brotli via native extension. |
| Circuit breaker stuck in open state | Constant X-Circuit-Breaker-State: open |
Increase timeout or lower failure_threshold. |
| Health probe flapping | Alternating 200/503 on /health |
Enable cache_results and review thresholds. |
Maintaining automated tests and observability pipelines for these edge cases ensures the API stack remains predictable even under extreme load.