Introduction

The Express compression middleware is designed to compress HTTP responses using gzip or deflate when the client supports it. However, it often appears to not work -- responses are sent uncompressed despite the middleware being installed. This happens because compression has several conditions that must all be met: the response must exceed a minimum size threshold (default 1KB), the Content-Type must be compressible, the client must send Accept-Encoding: gzip, and the middleware must be placed before any middleware that writes to the response. When any condition is not met, the response passes through uncompressed, wasting bandwidth and increasing page load times.

Symptoms

Response is not compressed:

bash
$ curl -H "Accept-Encoding: gzip" -I http://localhost:3000/api/data
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 2048
# Missing: Content-Encoding: gzip  <-- Response not compressed!

Or compression only works for some routes:

```bash $ curl -H "Accept-Encoding: gzip" -I http://localhost:3000/index.html Content-Encoding: gzip # Compressed - HTML is compressible

$ curl -H "Accept-Encoding: gzip" -I http://localhost:3000/api/users # No Content-Encoding # Not compressed - why? ```

Common Causes

  • Middleware placed too late: Compression middleware must be before route handlers
  • Response below threshold: Default threshold is 1KB -- small responses are not compressed
  • Content-Type not compressible: compression only compresses known text-based types
  • Client does not send Accept-Encoding: Some API clients do not request compression
  • Response already compressed: Images, already-gzipped files should not be double-compressed
  • Another middleware writes before compression: morgan or custom middleware sends headers first

Step-by-Step Fix

Step 1: Correct middleware ordering

```javascript const express = require('express'); const compression = require('compression');

const app = express();

// Compression MUST be one of the first middleware // Before ANY middleware that reads/writes the response app.use(compression());

// Then your other middleware app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(morgan('combined'));

// Then routes app.use('/api', apiRouter); app.use(express.static('public')); ```

Step 2: Configure threshold and filter

```javascript app.use(compression({ // Compress responses larger than 256 bytes (default is 1024) threshold: 256,

// Custom filter to determine what to compress filter: (req, res) => { // Always compress JSON API responses if (req.headers['x-no-compression']) { return false; }

// Use the default filter for Content-Type checking return compression.filter(req, res); },

// Compression level (1-9, higher = smaller but slower) level: 6,

// Memory allocation for compression memLevel: 8, })); ```

Step 3: Verify compression is working

```bash # Check response headers $ curl -H "Accept-Encoding: gzip" -v http://localhost:3000/api/users 2>&1 | grep -i encoding < Content-Encoding: gzip

# Compare sizes $ curl -H "Accept-Encoding: gzip" -o /dev/null -w "Size: %{size_download}\n" http://localhost:3000/api/users Size: 4523

$ curl -H "Accept-Encoding: identity" -o /dev/null -w "Size: %{size_download}\n" http://localhost:3000/api/users Size: 18234

# Compression ratio: 4523/18234 = 75% reduction ```

Step 4: Add compression to Express static files

```javascript // Static file serving with compression app.use(express.static('public', { // Let compression middleware handle compression // Do NOT use the 'compress' option here - it conflicts }));

// The compression middleware installed earlier will compress // static files served through express.static ```

Prevention

  • Always place compression() before route handlers and other middleware
  • Set threshold to 256 bytes or lower to compress even small JSON responses
  • Verify compression in CI by checking Content-Encoding: gzip header in test responses
  • Monitor compression ratio in production -- anything below 50% may indicate already-compressed content
  • Use compression.filter to exclude binary content (images, videos, PDFs)
  • Add Accept-Encoding: gzip to all API client requests
  • Never use res.write() before compression middleware processes the response