CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-fastify--compress

Fastify plugin providing compression and decompression utilities for HTTP responses and requests.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

request-decompression.mddocs/

Request Decompression

Automatic decompression of compressed request payloads with support for multiple encoding algorithms, forced encoding modes, and comprehensive error handling.

Capabilities

Automatic Request Decompression

When enabled globally or per-route, the plugin automatically decompresses incoming request payloads based on Content-Encoding headers.

// Automatic decompression is enabled via plugin registration
// Handled by preParsing hooks - no additional API calls needed

Decompression Process:

  • Checks Content-Encoding header in incoming requests
  • Validates encoding against supported algorithms
  • Applies appropriate decompression stream
  • Passes decompressed payload to Fastify's body parser
  • Handles decompression errors with configurable error handlers

Usage Examples:

const fastify = require('fastify')({ logger: true });

// Enable request decompression globally
await fastify.register(require('@fastify/compress'), {
  global: true,
  requestEncodings: ['gzip', 'deflate', 'br']
});

// This route will automatically decompress compressed request bodies
fastify.post('/api/data', async (request, reply) => {
  // request.body is automatically decompressed
  console.log('Received data:', request.body);
  return { received: Object.keys(request.body).length };
});

// Send compressed request:
// curl -X POST http://localhost:3000/api/data \
//   -H "Content-Encoding: gzip" \
//   -H "Content-Type: application/json" \
//   --data-binary @<(echo '{"large":"data"}' | gzip)

Supported Decompression Algorithms

The plugin supports the same algorithms for request decompression as response compression.

/**
 * Supported decompression encoding identifiers
 */
type EncodingToken = 'zstd' | 'br' | 'gzip' | 'deflate' | 'identity';

Algorithm Support:

  • gzip - Gzip decompression using Node.js zlib.createGunzip()
  • deflate - Deflate decompression using Node.js zlib.createInflate()
  • br - Brotli decompression using Node.js zlib.createBrotliDecompress()
  • zstd - Zstandard decompression (Node.js 22.15+/23.8+) using zlib.createZstdDecompress()
  • identity - No decompression (passthrough)

Usage Examples:

// Configure supported decompression algorithms
await fastify.register(require('@fastify/compress'), {
  requestEncodings: ['gzip', 'deflate'] // Only support gzip and deflate for requests
});

// Different encodings will be handled automatically:
// Content-Encoding: gzip -> uses createGunzip()
// Content-Encoding: deflate -> uses createInflate()  
// Content-Encoding: br -> rejected (not in requestEncodings)

Forced Encoding Mode

Force decompression using a specific algorithm regardless of Content-Encoding header.

/**
 * Force specific encoding for request decompression
 */
interface ForcedEncodingOptions {
  /** Force specific encoding for decompression, ignoring Content-Encoding header */
  forceRequestEncoding?: EncodingToken;
}

Usage Examples:

// Force all requests to be treated as gzip-compressed
await fastify.register(require('@fastify/compress'), {
  forceRequestEncoding: 'gzip',
  requestEncodings: ['gzip'] // Must include forced encoding
});

// Route-level forced encoding
fastify.post('/api/gzip-only', {
  decompress: {
    forceRequestEncoding: 'gzip'
  }
}, async (request, reply) => {
  // All request bodies treated as gzip-compressed
  // regardless of Content-Encoding header
  return { decompressed: request.body };
});

Route-Level Decompression Control

Configure decompression behavior on a per-route basis, overriding global settings.

/**
 * Route-level decompression configuration
 */
interface RouteOptions {
  /** Route-specific decompression options, or false to disable */
  decompress?: RouteDecompressOptions | false;
}

type RouteDecompressOptions = Pick<FastifyCompressOptions,
  | 'forceRequestEncoding'
  | 'onInvalidRequestPayload'
  | 'onUnsupportedRequestEncoding'
  | 'requestEncodings'
  | 'zlib'
>;

Usage Examples:

// Route with custom decompression settings
fastify.post('/api/secure-upload', {
  decompress: {
    requestEncodings: ['br'], // Only accept Brotli compression
    onUnsupportedRequestEncoding: (encoding, request) => {
      return new Error(`Secure endpoint requires Brotli compression, got: ${encoding}`);
    }
  }
}, async (request, reply) => {
  return { received: 'secure data' };
});

// Route with decompression disabled
fastify.post('/api/raw', {
  decompress: false
}, async (request, reply) => {
  // request.body will be the raw compressed data
  return { size: request.body.length };
});

// Route with forced encoding and custom error handling
fastify.post('/api/legacy', {
  decompress: {
    forceRequestEncoding: 'deflate',
    onInvalidRequestPayload: (encoding, request, error) => {
      request.log.error(`Legacy endpoint decompression failed: ${error.message}`);
      return new Error('Invalid legacy format payload');
    }
  }
}, async (request, reply) => {
  return { processed: request.body };
});

Error Handling

Comprehensive error handling for unsupported encodings and decompression failures.

/**
 * Handler for unsupported request Content-Encoding values
 * @param encoding - The unsupported encoding from Content-Encoding header
 * @param request - Fastify request object
 * @param reply - Fastify reply object
 * @returns Error to throw, or null/undefined to use default error
 */
type UnsupportedRequestEncodingHandler = (
  encoding: string,
  request: FastifyRequest,
  reply: FastifyReply
) => Error | undefined | null;

/**
 * Handler for invalid compressed request payloads
 * @param encoding - The encoding that failed to decompress
 * @param request - Fastify request object  
 * @param error - The decompression error
 * @returns Error to throw, or null/undefined to use default error
 */
type InvalidRequestPayloadHandler = (
  encoding: string,
  request: FastifyRequest,
  error: Error
) => Error | undefined | null;

Default Error Responses:

/**
 * Default error for unsupported Content-Encoding
 */
class InvalidRequestEncodingError extends Error {
  name: 'FastifyCompressError';
  code: 'FST_CP_ERR_INVALID_CONTENT_ENCODING';
  statusCode: 415;
  message: 'Unsupported Content-Encoding: {encoding}';
}

/**
 * Default error for invalid compressed payload
 */
class InvalidRequestCompressedPayloadError extends Error {
  name: 'FastifyCompressError';
  code: 'FST_CP_ERR_INVALID_CONTENT';
  statusCode: 400;
  message: 'Could not decompress the request payload using the provided encoding';
}

Usage Examples:

// Custom error handling
await fastify.register(require('@fastify/compress'), {
  requestEncodings: ['gzip', 'deflate', 'br'],
  
  onUnsupportedRequestEncoding: (encoding, request) => {
    // Log unsupported encoding attempts
    request.log.warn(`Rejected unsupported encoding: ${encoding} from ${request.ip}`);
    
    // Return custom error
    const error = new Error(`Compression format '${encoding}' is not supported`);
    error.statusCode = 415;
    return error;
  },
  
  onInvalidRequestPayload: (encoding, request, error) => {
    // Log decompression failures with context
    request.log.error({
      encoding,
      contentLength: request.headers['content-length'],
      error: error.message
    }, 'Request decompression failed');
    
    // Return custom error with details
    const customError = new Error(`Invalid ${encoding} compressed data: ${error.message}`);
    customError.statusCode = 400;
    customError.code = 'INVALID_COMPRESSED_PAYLOAD';
    return customError;
  }
});

// Handle errors in routes
fastify.post('/api/upload', async (request, reply) => {
  try {
    // Process decompressed request body
    return { processed: request.body };
  } catch (error) {
    if (error.code === 'FST_CP_ERR_INVALID_CONTENT_ENCODING') {
      return reply.status(415).send({ 
        error: 'Unsupported compression format',
        supported: ['gzip', 'deflate', 'br']
      });
    }
    throw error;
  }
});

Decompression Stream Processing

The plugin handles streaming decompression for large request payloads efficiently.

Stream Processing Features:

  • Automatic stream pipeline creation
  • Memory-efficient processing of large payloads
  • Error propagation through stream pipeline
  • Encoded length tracking for monitoring

Usage Examples:

// Handle large compressed uploads
fastify.post('/api/large-upload', {
  decompress: {
    requestEncodings: ['gzip', 'br']
  }
}, async (request, reply) => {
  // Plugin automatically handles streaming decompression
  // request.body contains the fully decompressed payload
  
  console.log(`Received ${request.body.length} bytes after decompression`);
  return { status: 'received' };
});

// Monitor decompression progress (advanced usage)
fastify.addHook('preParsing', async (request, reply, payload) => {
  if (request.headers['content-encoding']) {
    // Track original compressed size
    const originalSize = parseInt(request.headers['content-length'] || '0');
    request.log.info(`Decompressing ${originalSize} bytes of ${request.headers['content-encoding']} data`);
  }
});

Content-Type Considerations

Request decompression works independently of Content-Type headers, focusing purely on Content-Encoding.

Usage Examples:

// Decompress various content types
fastify.post('/api/json', async (request, reply) => {
  // Content-Type: application/json
  // Content-Encoding: gzip
  // -> Decompressed JSON in request.body
  return { received: request.body };
});

fastify.post('/api/form', async (request, reply) => {
  // Content-Type: application/x-www-form-urlencoded  
  // Content-Encoding: deflate
  // -> Decompressed form data in request.body
  return { fields: Object.keys(request.body) };
});

fastify.post('/api/binary', async (request, reply) => {
  // Content-Type: application/octet-stream
  // Content-Encoding: br
  // -> Decompressed binary data in request.body
  return { size: request.body.length };
});

Integration with Fastify Body Parsing

Request decompression integrates seamlessly with Fastify's built-in body parsing through the preParsing hook.

Processing Order:

  1. preParsing hook: Decompresses request payload
  2. Fastify body parser: Parses decompressed payload based on Content-Type
  3. Route handler: Receives parsed body in request.body

Usage Examples:

// JSON payload decompression and parsing
fastify.post('/api/json-data', async (request, reply) => {
  // 1. Plugin decompresses gzip/deflate/br payload
  // 2. Fastify parses JSON from decompressed data
  // 3. request.body contains parsed JavaScript object
  
  console.log('Parsed object:', request.body);
  return { keys: Object.keys(request.body) };
});

// Multipart form decompression
fastify.register(require('@fastify/multipart'));

fastify.post('/api/form-upload', async (request, reply) => {
  // 1. Plugin decompresses compressed multipart data  
  // 2. @fastify/multipart parses form fields and files
  // 3. request.body contains multipart data
  
  const parts = request.parts();
  for await (const part of parts) {
    console.log('Form field:', part.fieldname);
  }
  
  return { status: 'processed' };
});

docs

index.md

plugin-configuration.md

request-decompression.md

response-compression.md

tile.json