These libraries handle ZIP file creation and extraction in Node.js, but they serve different architectural needs. archiver is the industry standard for streaming large files without crashing memory. adm-zip offers a simple, synchronous API for small, local files but struggles with streams. yauzl and yazl are low-level, high-performance tools built specifically for streaming data, often used as building blocks for other tools. node-zip is deprecated and should be avoided. zip-lib provides a modern, promise-based wrapper around native bindings, useful when you need specific compression features not found in pure JS solutions.
Handling ZIP files in Node.js is a common requirement, from bundling assets for download to processing user uploads. However, not all ZIP libraries are built the same. The critical difference lies in how they handle data: do they load the whole file into memory, or do they stream it piece by piece? Let's break down the technical realities of adm-zip, archiver, node-zip, yauzl, yazl, and zip-lib.
Before diving into the viable options, we must address node-zip. This package is deprecated and no longer maintained. It relies on older native bindings that often fail to compile on modern Node.js versions.
// ā DO NOT USE
// const zip = require('node-zip');
// This package is abandoned and poses security risks.
Recommendation: If you see node-zip in a legacy codebase, plan a migration to archiver or yazl immediately. Do not start new projects with it.
The most important architectural decision is between loading the entire file into RAM (Buffer) versus processing it as a Stream.
adm-zip loads everything into memory. It is synchronous and blocking by default.
// adm-zip: Loads entire file into RAM
const AdmZip = require('adm-zip');
const zip = new AdmZip();
// Adds file to memory buffer immediately
zip.addLocalFile("./large-video.mp4");
// Blocks the event loop until writing is done
zip.writeZip("./output.zip");
archiver uses streams. It processes data in chunks.
// archiver: Streams data directly to disk or network
const archiver = require('archiver');
const output = require('fs').createWriteStream('./output.zip');
const archive = archiver('zip');
output.on('close', () => console.log('Done'));
archive.pipe(output);
// Streams file content without loading it all into RAM
archive.file('./large-video.mp4', { name: 'video.mp4' });
archive.finalize();
yazl and yauzl are also stream-based but offer a lower-level API.
yazl (writer) and yauzl (reader) give you raw control over the ZIP structure.archiver but require you to manage more details manually.// yazl: Low-level streaming write
const yazl = require('yazl');
const zipFile = new yazl.ZipFile();
const output = require('fs').createWriteStream('./output.zip');
zipFile.outputStream.pipe(output);
// You must explicitly end the stream
zipFile.addFile('./large-video.mp4', 'video.mp4');
zipFile.end();
zip-lib attempts to bridge the gap with a Promise-based API, often wrapping native libraries.
adm-zip.// zip-lib: Promise-based API
const { zip, unzip } = require('zip-lib');
// Returns a promise, non-blocking
await zip(['./large-video.mp4'], './output.zip');
How you add files to the archive varies significantly between high-level and low-level libraries.
adm-zip makes it trivial to add local folders recursively.
// adm-zip: Recursive folder add
const zip = new AdmZip();
// Automatically adds all files in 'src' folder
zip.addLocalFolder("./src", "dist/src");
archiver also supports recursive directory appending easily via plugins or built-in methods.
// archiver: Recursive directory append
const archive = archiver('zip');
// Streams all files in 'src' folder
archive.directory('./src', 'dist/src');
archive.finalize();
yazl requires you to manually handle file discovery if you want to add a folder.
readdir or glob to find files first.// yazl: Manual file addition (no built-in recursive folder add)
const fs = require('fs');
const path = require('path');
const yazl = require('yazl');
const zipFile = new yazl.ZipFile();
// You must manually iterate files
const files = ['file1.txt', 'file2.txt'];
files.forEach(f => zipFile.addFile(f, path.basename(f)));
zipFile.end();
zip-lib provides a clean array-based input for multiple files.
yazl for multiple files but less flexible than archiver for complex glob patterns.// zip-lib: Array of files
const { zip } = require('zip-lib');
// Zips specific files listed in array
await zip(['file1.txt', 'file2.txt'], './output.zip');
Reading archives has similar trade-offs between ease of use and memory safety.
adm-zip extracts synchronously to the disk.
// adm-zip: Sync extraction
const zip = new AdmZip("./archive.zip");
// Blocks execution until all files are written
zip.extractAllTo("./target-folder", true);
yauzl is the gold standard for streaming extraction.
// yauzl: Streaming extraction
const yauzl = require('yauzl');
yauzl.open("./archive.zip", { lazyEntries: true }, (err, zipfile) => {
zipfile.on('entry', (entry) => {
zipfile.openReadStream(entry, (err, readStream) => {
// Pipe directly to file or processing logic
readStream.pipe(require('fs').createWriteStream(entry.fileName));
zipfile.readEntry(); // Read next entry
});
});
});
archiver is primarily for creation. For extraction, you would typically use yauzl or unzip-stream alongside it.
zip-lib offers a simple async extract function similar to adm-zip but non-blocking.// zip-lib: Async extraction
const { unzip } = require('zip-lib');
// Returns a promise, doesn't block the event loop
await unzip('./archive.zip', './target-folder');
You need to generate a ZIP of 500MB of logs and send it to the browser.
archiverres). adm-zip would try to load 500MB into RAM, likely crashing your server instance.// Express example with archiver
app.get('/download', (req, res) => {
const archive = archiver('zip');
res.setHeader('Content-Type', 'application/zip');
res.setHeader('Content-Disposition', 'attachment; filename=logs.zip');
archive.pipe(res); // Stream directly to client
archive.directory('/var/logs', false);
archive.finalize();
});
You are writing a command-line tool to bundle a small project folder (under 10MB) for deployment.
adm-zip// CLI script with adm-zip
const zip = new AdmZip();
zip.addLocalFolder('./dist');
zip.writeZip('./release.zip');
console.log('Build complete!');
You need to unzip a user upload in a Lambda function where cold start time and bundle size matter.
yauzl / yazlzip-lib might require compiling native binaries which can be tricky in serverless environments depending on the runtime.// Lambda handler with yauzl
exports.handler = async (event) => {
// Process stream efficiently with minimal bundle size
// ... implementation using yauzl events ...
};
| Feature | adm-zip | archiver | yauzl / yazl | zip-lib | node-zip |
|---|---|---|---|---|---|
| Primary Mode | Synchronous / Buffer | Streaming | Streaming | Async / Promise | Native / Deprecated |
| Memory Usage | High (Loads all) | Low (Chunks) | Low (Chunks) | Medium | High |
| Ease of Use | āāāāā | āāāā | āā | āāā | āāā |
| Recursive Dir | Built-in | Built-in | Manual | Limited | Built-in |
| Best For | CLI, Small Files | Web Servers, Large Files | High Perf, Custom Logic | Modern Async Apps | None (Avoid) |
The choice comes down to data size and execution context.
If you are building a web server or handling large files, archiver is the safest and most robust choice. Its streaming architecture protects your application from memory spikes, and its API is mature enough to handle complex directory structures without much fuss.
If you are writing small scripts, CLI tools, or dealing with tiny files locally, adm-zip remains the king of simplicity. Its synchronous API removes the need for complex async/await chains for trivial tasks.
For specialized high-performance needs or serverless environments where every kilobyte of bundle size counts, the yauzl/yazl duo provides the leanest, most efficient foundation, though it demands more code from the developer.
Avoid node-zip entirely. It belongs to the past. For a modern, promise-based experience with native speed, zip-lib is a solid alternative if archiver feels too heavy for your specific use case.
Choose adm-zip for simple scripts, CLI tools, or build steps where you need to zip small folders synchronously and don't care about streaming. It is perfect for local file manipulation where the entire file fits comfortably in RAM. Avoid it for web servers handling large uploads or downloads, as it loads everything into memory at once.
Choose archiver when building web servers, APIs, or data pipelines that need to stream ZIP files directly to the user or disk. It is the best choice for handling large datasets because it processes data in chunks, preventing memory crashes. Its plugin ecosystem also makes it ideal if you need to support formats beyond standard ZIP.
Do NOT choose node-zip for any new project. It is officially deprecated and no longer maintained. Using it introduces security risks and compatibility issues with modern Node.js versions. Migrate existing projects to archiver or yazl immediately.
Choose yauzl when you need maximum performance and control for reading (extracting) ZIP files via streams. It is ideal for backend services that process uploaded archives where memory efficiency is critical. Be prepared to write more boilerplate code compared to higher-level libraries like adm-zip.
Choose yazl when you need a lightweight, pure-JavaScript solution for creating ZIP files via streams without the extra features of archiver. It is perfect for microservices or serverless functions where bundle size matters and you only need basic ZIP creation capabilities with high efficiency.
Choose zip-lib if you require a modern, Promise-based API and need access to native compression algorithms for better speed or ratio. It is a good middle ground if you find archiver too complex but need more robustness than adm-zip offers for medium-sized files.
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.