Network bandwidth is expensive. Every kilobyte matters when you're serving millions of API requests. Gzip compression can reduce response sizes by 70-80% for typical JSON payloads. But compression has a cost: it takes CPU cycles. This middleware is smart about when to compress, and it only compresses when the benefit outweighs the cost.
The middleware checks five conditions before compressing:
-
Only GET requests: Compression makes sense for read operations that return data. POST/PUT/DELETE requests typically have small bodies and small responses, so compression overhead isn't worth it.
-
Only successful responses (200-299): Compress data responses, not error messages. A 400 or 500 error with a 200-byte message isn't worth compressing.
-
Client must accept gzip: Check the
Accept-Encodingheader. If it doesn't includegzip, the client can't decompress the response, so don't compress. Most modern clients support gzip, but browsers and older clients might not. -
Response not already encoded: Check if
Content-Encodingis already set. If another middleware already compressed the response, don't double-compress. -
Response is large enough: Only compress if the response is at least 1400 bytes. Compressing tiny responses adds overhead (CPU time, header bytes) that outweighs the size savings. A 200-byte JSON response becomes 300+ bytes after compression headers and overhead.
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// Only compress GET with success status (200-299)
if ($request->getMethod() !== 'GET') {
return $response;
}
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
return $response;
}
// Check if client accepts gzip
$acceptEncoding = $request->header('Accept-Encoding', '');
if (! str_contains($acceptEncoding, 'gzip')) {
return $response;
}
// Don't compress if already encoded
if ($response->headers->has('Content-Encoding')) {
return $response;
}
$content = $response->getContent();
// Only compress if content is large enough
if (strlen($content) < self::MIN_COMPRESSION_SIZE) {
return $response;
}
// Compress with gzip compression level 9 (maximum)
$compressed = gzencode($content, 9);
$response->setContent($compressed);
$response->headers->set('Content-Encoding', 'gzip');
return $response;
}
The middleware uses gzencode() with compression level 9, which is maximum compression. This takes longer than level 6 (default) but produces smaller files. For APIs, the extra CPU is worth the bandwidth savings, especially for large responses.
Imagine a license listing endpoint that returns 100 licenses with full details. The JSON response might be 50KB. With gzip:
- Uncompressed: 50KB transmitted
- Compressed: ~8KB transmitted (85% reduction)
- Compression overhead: typically a few milliseconds of CPU on the server
- Decompression: Automatic in the browser/client, feels instant
For a global API serving thousands of requests per day, this is a massive bandwidth saving. On a 100Mbps connection, you send that response 6.25x faster. On a mobile connection, it's the difference between instant and noticeably slow.
Clients decoding gzip is automatic. When a browser or HTTP client sees Content-Encoding: gzip, it automatically decompresses. The client code doesn't have to do anything special. In fact, most clients never even think about it, the decompression is transparent.
Some responses shouldn't be compressed:
- Already compressed: Images, PDFs, videos are already compressed; re-compressing wastes CPU.
- Streaming responses: If you're streaming a large file, compression happens transparently in the HTTP layer, not in your middleware.
- Server-Sent Events (SSE): Compressing streaming data breaks the real-time nature; each chunk gets compressed/decompressed separately.
For a typical REST API returning JSON, compression is almost always beneficial.
Because compression should be consistent across all API responses, this middleware is best registered globally in the API middleware group (not the web group).
Here's the magic: you can add, remove, or reorder middlewares without changing your controller code. You can add a new security check, a new logging mechanism, or a new feature that applies to all routes simply by adding a line to your middleware stack. No controller changes needed.