or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.mdplugin-configuration.mdrequest-decompression.mdresponse-compression.md
tile.json

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' };
});