These libraries provide tools for creating, reading, and manipulating ZIP archives in Node.js environments. They vary significantly in their approach: some operate entirely in memory using buffers, while others use streams to handle large files efficiently. Some focus solely on compression (writing), others on decompression (reading), and a few offer both. Choosing the right tool depends on whether you need to process large files without loading them into RAM, whether you need high-level convenience or low-level control, and whether you are building a server-side pipeline or a simple script.
Working with ZIP files in Node.js is a common requirement for build tools, backup systems, and file download services. However, not all ZIP libraries are built the same. Some load entire files into memory, while others process data in streams. Some only write archives, while others only read them. Making the wrong choice can lead to memory crashes on large files or security vulnerabilities like Zip Slip.
Let's compare how these seven packages handle the core challenges of archive management.
The most critical architectural decision is how the library handles data. Loading a 2GB ZIP file into memory will crash a server. Streaming processes data in chunks, keeping memory usage low.
adm-zip loads the whole file into a buffer.
// adm-zip: Loads entire file into memory
const AdmZip = require('adm-zip');
const zip = new AdmZip("/path/to/archive.zip");
const entries = zip.getEntries(); // All in RAM
archiver uses streams for output.
// archiver: Streams output
const archiver = require('archiver');
const output = fs.createWriteStream('archive.zip');
const archive = archiver('zip');
archive.pipe(output);
archive.directory('/path/to/dir', false);
archive.finalize();
compressing supports streaming for both input and output.
// compressing: Streaming compression
const compressing = require('compressing');
await compressing.zip.compressDir('/src', '/dest.zip');
node-zip typically buffers data.
// node-zip: Buffer based
const zip = new Zip(data, { base64: false });
// Processing happens on the buffer object
yauzl is a read-stream library.
// yauzl: Read stream
yauzl.open("archive.zip", { lazyEntries: true }, (err, zipfile) => {
zipfile.readEntry();
zipfile.on("entry", (entry) => {
zipfile.openReadStream(entry, (err, readStream) => {
readStream.pipe(fs.createWriteStream(entry.fileName));
});
});
});
yazl is a write-stream library.
// yazl: Write stream
const yazl = require("yazl");
const zipFile = new yazl.ZipFile();
zipFile.addFile("/path/to/file.txt", "file.txt");
zipFile.outputStream.pipe(fs.createWriteStream("output.zip"));
zipFile.end();
zip-lib uses Promises with internal streaming.
// zip-lib: Promise-based
const zipLib = require('zip-lib');
await zipLib.archiveFolder('/src', '/dest.zip');
Not every library can do both. Some are specialized for creating archives, while others are specialized for extracting them.
| Package | Read (Unzip) | Write (Zip) | Notes |
|---|---|---|---|
adm-zip | ā | ā | In-memory only |
archiver | ā | ā | Write-only, streaming |
compressing | ā | ā | Streaming, high-level |
node-zip | ā | ā | Legacy, buffer-based |
yauzl | ā | ā | Read-only, low-level |
yazl | ā | ā | Write-only, low-level |
zip-lib | ā | ā | Promise-based, streaming |
If you need to both zip and unzip in the same service, adm-zip, compressing, or zip-lib are your only single-dependency options. If you only need to generate downloads, archiver is the most robust choice.
Developer experience varies wildly. High-level libraries hide stream management, while low-level libraries expose it for control.
High-Level Convenience
compressing and zip-lib offer the simplest APIs. You call a function, and it handles the streams for you.
// compressing: One-line extract
await compressing.zip.uncompress('archive.zip', '/output/dir');
// zip-lib: One-line extract
await zipLib.unarchive('archive.zip', '/output/dir');
Low-Level Control
yauzl and yazl require you to manage entry events and stream pipes. This is more work but allows custom logic, like filtering specific files during extraction.
// yauzl: Manual entry filtering
zipfile.on("entry", (entry) => {
if (entry.fileName.endsWith('.txt')) {
// Process only text files
zipfile.openReadStream(entry, ...);
} else {
zipfile.readEntry(); // Skip
}
});
Middle Ground
adm-zip is simple but blocks the event loop if files are large because it is synchronous.
// adm-zip: Synchronous extraction
zip.extractAllTo("/output/dir", true);
A critical security issue in ZIP handling is "Zip Slip," where a malicious archive contains paths like ../../evil.sh to write files outside the intended directory.
adm-zip: Has had historical vulnerabilities. You must validate entry names manually before extracting.archiver: Safe for creation, but does not handle extraction.compressing: Includes protections against Zip Slip in recent versions.yauzl: Does not automatically prevent Zip Slip. You must check entry.fileName before writing.zip-lib: Generally safer defaults but always validate paths in production.// Safe extraction pattern (required for yauzl/adm-zip)
const path = require('path');
const dest = path.resolve('/output/dir', entry.fileName);
if (!dest.startsWith('/output/dir')) {
throw new Error('Invalid path');
}
Maintenance matters for security patches and Node.js compatibility.
node-zip: ā ļø Legacy. This package is no longer actively maintained. It relies on older patterns and may not work well with modern Node.js versions. Do not use for new projects.adm-zip: ā ļø Caution. Widely used but has a history of security issues. Use only for trusted local files, not user uploads.archiver: ā
Active. The standard for streaming creation.yauzl / yazl: ā
Active. Maintained by experts, very stable.compressing: ā
Active. Maintained by the Egg.js team.zip-lib: ā
Active. Modern alternative gaining traction.| Feature | adm-zip | archiver | compressing | yauzl/yazl | zip-lib |
|---|---|---|---|---|---|
| Model | Buffer | Stream | Stream | Stream | Stream/Promise |
| Read/Write | Both | Write Only | Both | Separate | Both |
| Async | Sync | Stream Events | Async/Promise | Stream Events | Async/Promise |
| Difficulty | Easy | Medium | Easy | Hard | Easy |
| Safety | Manual Check | N/A | Built-in | Manual Check | Built-in |
archiver is the go-to for generating downloads ā it streams directly to the client without eating RAM.
compressing and zip-lib are best for backend processing where you need to unzip user uploads safely and asynchronously.
yauzl and yazl are for library authors who need to build custom tools on top of ZIP logic.
adm-zip is fine for local dev scripts but avoid it for user-facing servers.
node-zip should be retired from your stack.
Final Thought: For most modern web applications, prioritize streaming libraries (archiver, compressing, zip-lib) to ensure your server stays responsive under load. Always validate file paths during extraction to prevent security breaches.
Choose adm-zip for simple scripts where you need to read or write small ZIP files entirely in memory. It offers a synchronous API that is easy to use but does not scale well for large files or streaming HTTP responses due to high memory consumption.
Choose archiver when you need to stream ZIP creation, such as piping an archive directly to an HTTP response or a file write stream. It is the industry standard for generating archives on the fly but does not support extracting/unzipping files.
Choose compressing for a high-level streaming solution that supports both compression and decompression. It is ideal for Egg.js applications or when you need a consistent API for ZIP, TAR, and GZIP without managing low-level stream events.
Avoid node-zip for new projects. It is considered legacy software with limited maintenance. Modern alternatives like adm-zip or zip-lib offer better performance, security patches, and active support.
Choose yauzl when you need low-level, streaming access to read ZIP entries without loading the whole file into memory. It gives you full control over entry processing but requires more boilerplate code than high-level wrappers.
Choose yazl when you need low-level, streaming control over ZIP creation. It pairs well with yauzl and is suitable for building custom archive tools where you need to manage metadata and stream chunks manually.
Choose zip-lib for a modern, Promise-based API that handles both compression and decompression. It is a good middle-ground between the low-level control of yauzl/yazl and the simplicity of adm-zip, with better async support.
ADM-ZIP is a pure JavaScript implementation for zip data compression for NodeJS.
With npm do:
$ npm install adm-zip
Electron file system support described below.
The library allows you to:
There are no other nodeJS libraries that ADM-ZIP is dependent of
var AdmZip = require("adm-zip");
// reading archives
var zip = new AdmZip("./my_file.zip");
var password = "1234567890";
var zipEntries = zip.getEntries(); // an array of ZipEntry records - add password parameter if entries are password protected
zipEntries.forEach(function (zipEntry) {
console.log(zipEntry.toString()); // outputs zip entries information
if (zipEntry.entryName == "my_file.txt") {
console.log(zipEntry.getData().toString("utf8"));
}
});
// outputs the content of some_folder/my_file.txt
console.log(zip.readAsText("some_folder/my_file.txt"));
// extracts the specified file to the specified location
zip.extractEntryTo(/*entry name*/ "some_folder/my_file.txt", /*target path*/ "/home/me/tempfolder", /*maintainEntryPath*/ false, /*overwrite*/ true);
// extracts everything
zip.extractAllTo(/*target path*/ "/home/me/zipcontent/", /*overwrite*/ true);
// creating archives
var zip = new AdmZip();
// add file directly
var content = "inner content of the file";
zip.addFile("test.txt", Buffer.from(content, "utf8"), "entry comment goes here");
// add local file
zip.addLocalFile("/home/me/some_picture.png");
// get everything as a buffer
var willSendthis = zip.toBuffer();
// or write everything to disk
zip.writeZip(/*target file name*/ "/home/me/files.zip");
// ... more examples in the wiki
For more detailed information please check out the wiki.
ADM-ZIP has supported electron original-fs for years without any user interractions but it causes problem with bundlers like rollup etc. For continuing support original-fs or any other custom file system module. There is possible specify your module by fs option in ADM-ZIP constructor.
Example:
const AdmZip = require("adm-zip");
const OriginalFs = require("original-fs");
// reading archives
const zip = new AdmZip("./my_file.zip", { fs: OriginalFs });
.
.
.
Please report security vulnerabilities privately. See SECURITY.md.