decompress vs decompress-zip vs extract-zip vs unzipper
Extracting ZIP Archives in Node.js: Architecture and API Comparison
decompressdecompress-zipextract-zipunzipperSimilar Packages:

Extracting ZIP Archives in Node.js: Architecture and API Comparison

decompress, decompress-zip, extract-zip, and unzipper are Node.js utilities designed to handle ZIP archive extraction, but they serve different architectural needs. decompress acts as a high-level plugin-based system supporting multiple archive formats (ZIP, TAR, GZ) through a unified API. decompress-zip is a specific plugin for the decompress ecosystem focused solely on ZIP files with stream support. extract-zip is a standalone, zero-dependency utility optimized for simplicity and security, strictly handling ZIP files. unzipper is a powerful streaming parser that allows fine-grained control over individual entries within an archive, ideal for processing large files without writing to disk first.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
decompress0420-516 years agoMIT
decompress-zip0101-306 years agoMIT
extract-zip0398-576 years agoBSD-2-Clause
unzipper047356.8 kB912 months agoMIT

Extracting ZIP Archives in Node.js: Architecture and API Comparison

When building Node.js applications that handle file uploads, distribute software updates, or process data bundles, you will inevitably need to unpack ZIP archives. While the concept seems simple, the implementation details vary significantly between libraries. The four main contenders—decompress, decompress-zip, extract-zip, and unzipper—take different approaches to performance, security, and API design. Let's break down how they work and when to use each one.

🏗️ Architecture: Unified Plugins vs. Standalone Tools

The most fundamental difference lies in how these libraries are structured. Some are designed as part of a larger ecosystem, while others are focused, single-purpose tools.

decompress is a high-level wrapper that supports multiple formats. It does not handle compression itself; instead, it relies on plugins. You install the core package and then add specific plugins like decompress-zip or decompress-tar.

// decompress: Requires plugin registration
const decompress = require('decompress');
const decompressZip = require('decompress-zip');

// You must explicitly register the plugin for ZIP support
const result = await decompress('archive.zip', 'dist', {
  plugins: [decompressZip()]
});

decompress-zip is one of those plugins. It rarely stands alone. It is tightly coupled with the decompress API and shares its configuration style. Using it directly without the main decompress package is uncommon and often unnecessary.

// decompress-zip: Typically used internally by decompress
// Direct usage is verbose and less common in modern apps
const DecompressZip = require('decompress-zip');
const unzipper = new DecompressZip('archive.zip');

unzipper.on('error', (err) => console.error(err));
unzipper.on('extract', () => console.log('Done'));
unzipper.extract({ path: 'dist' });

extract-zip is a standalone tool. It has no plugins and no external format dependencies. You install it, and it works for ZIP files immediately. This reduces the risk of dependency hell and makes it very predictable.

// extract-zip: Zero-config, standalone usage
const extract = require('extract-zip');

async function run() {
  try {
    await extract('archive.zip', { dir: 'dist' });
    console.log('Extraction complete');
  } catch (err) {
    console.error('Extraction failed', err);
  }
}

unzipper takes a streaming approach. Instead of a simple "source to destination" function, it exposes the archive as a stream of entries. This gives you maximum control but requires more code to set up basic extraction.

// unzipper: Stream-based architecture
const fs = require('fs');
const path = require('path');
const unzipper = require('unzipper');

fs.createReadStream('archive.zip')
  .pipe(unzipper.Extract({ path: 'dist' }))
  .on('close', () => console.log('Extraction complete'));

🛡️ Security: Path Traversal and Validation

Security is critical when unpacking archives from untrusted sources. A common attack vector is "Zip Slip," where a file inside the ZIP has a path like ../../etc/passwd, potentially overwriting system files.

extract-zip has strong built-in protections against Zip Slip. It validates paths by default and throws an error if it detects traversal attempts. This makes it a safe default for handling user uploads.

// extract-zip: Automatic path validation
// If 'archive.zip' contains '../../evil.sh', this promise rejects
await extract('malicious.zip', { dir: './uploads' }); 
// No extra code needed; security is on by default

decompress and decompress-zip also address this, but historically, users had to be careful with plugin versions. In current versions, they sanitize paths, but the plugin architecture means you must ensure all installed plugins are up to date.

// decompress: Sanitization handled by core and plugins
await decompress('malicious.zip', 'dist', {
  // Filter can be used for extra custom validation
  filter: file => file.path.startsWith('safe-prefix/')
});

unzipper gives you the power to implement your own security checks because you control the stream. However, this also means if you write custom logic, you might accidentally introduce vulnerabilities if you don't validate paths manually.

// unzipper: Manual path validation required for custom logic
fs.createReadStream('archive.zip')
  .pipe(unzipper.Parse())
  .on('entry', function (entry) {
    const fileName = entry.path;
    // Developer must ensure fileName doesn't contain '..'
    if (fileName.includes('..')) {
      entry.autodrain(); // Skip dangerous files
    } else {
      entry.pipe(fs.createWriteStream(fileName));
    }
  });

💧 Streaming vs. Buffering: Handling Large Files

How the library manages memory is crucial when dealing with large archives (hundreds of MBs or GBs).

unzipper is built entirely on Node.js streams. It processes files chunk by chunk. This means you can extract a 5GB file on a server with only 512MB of RAM without crashing. It is the most efficient choice for large-scale data processing.

// unzipper: True streaming, low memory footprint
fs.createReadStream('huge-archive.zip')
  .pipe(unzipper.ParseOne('large-file.bin')) // Extract just one file
  .pipe(fs.createWriteStream('output.bin'));
// Memory usage remains constant regardless of file size

extract-zip uses streams internally but exposes a Promise-based API that resolves only when the whole operation is done. While efficient, it is designed for "extract all" scenarios rather than picking specific files mid-stream.

// extract-zip: Efficient but waits for completion
// Good for medium-to-large files, but less flexible than raw streams
await extract('huge-archive.zip', { dir: './data' });

decompress generally buffers more than unzipper because of its plugin abstraction layer. While it can handle reasonably large files, it is not the first choice for extreme performance scenarios involving massive archives.

// decompress: Higher level abstraction
// Suitable for typical web app file sizes ( < 500MB )
const files = await decompress('large-archive.zip', 'dist');
console.log(`Extracted ${files.length} files`);

🔍 Granular Control: Reading Specific Entries

Sometimes you don't want to extract everything. You might just need to read config.json from inside a ZIP without unpacking the rest.

unzipper excels here. You can parse the archive, find a specific entry by name, and pipe it directly to a string parser or another stream.

// unzipper: Read a single file without extracting others
const fs = require('fs');
const unzipper = require('unzipper');

fs.createReadStream('archive.zip')
  .pipe(unzipper.Parse())
  .on('entry', function (entry) {
    if (entry.path === 'config.json') {
      entry.pipe(process.stdout); // Output content directly
    } else {
      entry.autodrain(); // Skip other files
    }
  });

extract-zip does not support reading individual files without extracting the whole archive first. You would have to extract to a temp folder and then read the file, which is slower and uses more disk I/O.

// extract-zip: Must extract all to read one
await extract('archive.zip', { dir: './temp' });
const config = fs.readFileSync('./temp/config.json', 'utf8');

decompress returns an array of file objects in memory after extraction. You can filter this array, but the extraction process usually happens fully before you get the result.

// decompress: Filter results after extraction
const files = await decompress('archive.zip');
const configFile = files.find(f => f.path === 'config.json');
if (configFile) {
  console.log(configFile.data.toString());
}

📦 Maintenance and Deprecation Status

Before choosing a library, you must check its current maintenance status. Using abandoned packages introduces security risks.

decompress-zip and the original decompress ecosystem have seen periods of low activity. While still functional, many developers have migrated to more actively maintained alternatives for critical infrastructure. Always check the "Last Publish" date on npm before installing.

unzipper is a fork of the older node-unzip package. It was created specifically to fix bugs and add maintenance to the original abandoned project. It is generally considered the standard for streaming ZIP operations in Node.js today.

extract-zip is widely used in the Electron and CLI tooling communities. It receives regular updates to handle new Node.js versions and security patches, making it a reliable choice for standard extraction tasks.

📊 Summary: Key Differences

Featuredecompressdecompress-zipextract-zipunzipper
Primary UseMulti-format extractionZIP plugin for decompressSimple ZIP extractionStreaming & Granular Control
API StylePromise-based (Plugin)Event-based / PluginPromise-basedStream-based
Memory UsageModerateModerateLow-ModerateVery Low (Streaming)
SecurityGood (Sanitized)Good (Sanitized)Excellent (Built-in)Manual (Flexible)
Single File ReadNo (Extracts all)NoNoYes (Native)
FormatsZIP, TAR, GZ (via plugins)ZIP onlyZIP onlyZIP only

💡 The Big Picture

Choosing the right package depends on your specific constraints:

  • Go with extract-zip if you need a "set it and forget it" solution for unzipping files securely. It is perfect for build tools, installers, or backend services where you just need to unpack a known ZIP file to a folder.
  • Go with unzipper if you are dealing with large files, need to read specific files inside the archive without extracting everything, or want to pipe data directly into another process. It is the professional choice for data pipelines.
  • Go with decompress only if you genuinely need to support multiple archive formats (like TAR and GZ) with a single API. If you only need ZIP, the extra complexity of the plugin system is usually not worth it.
  • Avoid decompress-zip as a standalone dependency in new projects. It is largely superseded by extract-zip for simplicity or unzipper for performance.

Final Thought: For most modern frontend and Node.js backend architectures, extract-zip offers the best balance of safety and simplicity, while unzipper remains the undisputed king of high-performance streaming operations.

How to Choose: decompress vs decompress-zip vs extract-zip vs unzipper

  • decompress:

    Choose decompress if your application needs to handle multiple archive formats (ZIP, TAR, GZ) through a single, consistent API. It is ideal for general-purpose file upload handlers where the input format varies, and you prefer a plugin architecture that lets you swap compression algorithms without changing your core logic.

  • decompress-zip:

    Choose decompress-zip only if you are already committed to the decompress plugin ecosystem and need specific ZIP handling features not covered by the main wrapper. For most new projects, this is rarely the primary choice unless you are maintaining legacy code that relies on this specific plugin implementation.

  • extract-zip:

    Choose extract-zip for straightforward, secure ZIP extraction tasks where you want minimal dependencies and a 'batteries-included' approach. It is the best fit for CLI tools, build scripts, or backend services that simply need to unpack a ZIP file to a directory without requiring complex stream manipulation or support for other archive types.

  • unzipper:

    Choose unzipper when you need to process large archives via streams without loading them entirely into memory, or when you need to inspect, filter, or transform individual files inside the ZIP before extracting. It is essential for scenarios like reading specific configuration files from an archive on the fly or piping extracted content directly to another process.

README for decompress

decompress Build Status

Extracting archives made easy

See decompress-cli for the command-line version.

Install

$ npm install decompress

Usage

const decompress = require('decompress');

decompress('unicorn.zip', 'dist').then(files => {
	console.log('done!');
});

API

decompress(input, [output], [options])

Returns a Promise for an array of files in the following format:

{
	data: Buffer,
	mode: Number,
	mtime: String,
	path: String,
	type: String
}

input

Type: string Buffer

File to decompress.

output

Type: string

Output directory.

options

filter

Type: Function

Filter out files before extracting. E.g:

decompress('unicorn.zip', 'dist', {
	filter: file => path.extname(file.path) !== '.exe'
}).then(files => {
	console.log('done!');
});

Note that in the current implementation, filter is only applied after fully reading all files from the archive in memory. Do not rely on this option to limit the amount of memory used by decompress to the size of the files included by filter. decompress will read the entire compressed file into memory regardless.

map

Type: Function

Map files before extracting: E.g:

decompress('unicorn.zip', 'dist', {
	map: file => {
		file.path = `unicorn-${file.path}`;
		return file;
	}
}).then(files => {
	console.log('done!');
});
plugins

Type: Array
Default: [decompressTar(), decompressTarbz2(), decompressTargz(), decompressUnzip()]

Array of plugins to use.

strip

Type: number
Default: 0

Remove leading directory components from extracted files.

License

MIT © Kevin Mårtensson