adm-zip vs archiver vs jszip vs zip-local
Architecting ZIP Compression Strategies in Node.js
adm-ziparchiverjszipzip-localSimilar Packages:

Architecting ZIP Compression Strategies in Node.js

adm-zip, archiver, jszip, and zip-local are all Node.js libraries designed to create, read, and modify ZIP archives, but they solve different architectural problems. jszip is unique in its ability to run in both Node.js and web browsers, making it ideal for full-stack or client-side compression tasks. archiver is a streaming engine built for high-performance server-side operations, allowing developers to zip large files or directories without loading them entirely into memory. adm-zip offers a synchronous, in-memory API that is simple to use for small to medium-sized files but can cause performance bottlenecks with large datasets. zip-local provides a straightforward synchronous interface similar to adm-zip but has seen significantly less maintenance activity in recent years.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
adm-zip02,180140 kB552 months agoMIT
archiver02,97539.6 kB1724 months agoMIT
jszip010,379762 kB413-(MIT OR GPL-3.0-or-later)
zip-local012057.1 kB13--

Architecting ZIP Compression Strategies in Node.js

When building Node.js applications that handle file archives, choosing the right compression library is not just about syntax β€” it is about how your application manages memory, handles large files, and where the code actually runs. The four main contenders β€” adm-zip, archiver, jszip, and zip-local β€” take fundamentally different approaches to reading and writing ZIP files. Let's break down their architectures to help you make the right call for your system.

πŸ—οΈ Core Architecture: Streaming vs. In-Memory

The most critical decision you will make is between streaming data and loading everything into memory. This choice dictates whether your server can handle a 10MB file or a 10GB file without crashing.

archiver is built on streams. It reads data in small chunks and pipes it directly to the output. This means you can zip a massive directory without ever holding the whole thing in RAM.

// archiver: Streaming approach
const archiver = require('archiver');
const fs = require('fs');
const output = fs.createWriteStream('archive.zip');

const archive = archiver('zip', { zlib: { level: 9 } });

output.on('close', () => console.log('Done'));
archive.pipe(output);

// Append files directly from disk streams
archive.file('large-video.mp4', { name: 'video.mp4' });
archive.directory('big-folder/', false);

archive.finalize();

adm-zip loads the entire archive or file set into memory before processing. This is fast for small files but dangerous for large ones.

// adm-zip: In-memory approach
const AdmZip = require('adm-zip');

const zip = new AdmZip();

// Adds file content to memory immediately
zip.addLocalFile('large-video.mp4', 'video.mp4');
zip.addLocalFolder('big-folder');

// Blocks the event loop while writing
zip.writeZip('archive.zip');

jszip also operates primarily in memory, constructing the archive structure in RAM before generating the final output buffer. While it supports some streaming features in Node.js via external modules, its core design focuses on holding data in memory to facilitate browser compatibility.

// jszip: In-memory approach
const JSZip = require('jszip');
const fs = require('fs');

const zip = new JSZip();

// Reads file into memory
zip.file('video.mp4', fs.readFileSync('large-video.mp4'));
zip.folder('big-folder', fs.readFileSync('big-folder'));

// Generates a buffer in memory
zip.generateAsync({ type: 'nodebuffer' }).then(content => {
  fs.writeFileSync('archive.zip', content);
});

zip-local follows a synchronous, in-memory model similar to adm-zip. It reads files into memory and writes them out in a blocking fashion.

// zip-local: In-memory approach
const zip = require('zip-local');

// Sync operation blocks the thread
zip.sync.zip('large-video.mp4').compress().save('archive.zip');

🌐 Environment Support: Server vs. Browser

Where your code runs often limits your choices immediately. Most Node.js libraries rely on file system APIs that do not exist in a web browser.

jszip is the clear winner here. It is designed to run in the browser, Node.js, and even web workers. If you need to let users download a ZIP of their selected items directly from the browser without hitting your server, this is your only real option.

// jszip: Works in Browser and Node
// In a browser environment
zip.generateAsync({ type: 'blob' }).then(function(content) {
  // Force download in the browser
  const link = document.createElement('a');
  link.href = URL.createObjectURL(content);
  link.download = 'archive.zip';
  link.click();
});

archiver, adm-zip, and zip-local are strictly Node.js libraries. They depend on the fs (file system) and stream modules which are unavailable in standard browser environments. Attempting to bundle these for the frontend will result in build errors or require heavy polyfills that defeat their purpose.

⚑ Performance and Concurrency

When your application handles multiple requests simultaneously, blocking the main thread can bring your server to a halt.

archiver shines in high-concurrency scenarios. Because it streams data, it frees up the event loop to handle other requests while the OS manages the disk I/O. It does not block the thread while reading or writing.

// archiver: Non-blocking stream
app.get('/download', (req, res) => {
  const archive = archiver('zip');
  
  res.setHeader('Content-Type', 'application/zip');
  res.setHeader('Content-Disposition', 'attachment; filename=data.zip');
  
  // Pipes directly to HTTP response without buffering
  archive.pipe(res);
  archive.directory('/path/to/files', false);
  archive.finalize();
});

adm-zip and zip-local use synchronous methods like writeZip() or .save(). These methods block the Node.js event loop until the operation completes. If a user uploads a large file, no other requests can be processed on that thread until the ZIP is finished.

// adm-zip: Blocking operation
app.get('/download', (req, res) => {
  const zip = new AdmZip();
  zip.addLocalFolder('/path/to/files');
  
  // Blocks the server thread here
  zip.writeZip('temp.zip'); 
  
  res.download('temp.zip');
});

jszip uses Promises for its generation phase (generateAsync), which is non-blocking during the compression calculation. However, because it holds data in memory, it can still cause garbage collection pauses or memory pressure under heavy load, which indirectly affects performance.

// jszip: Async generation but memory heavy
app.get('/download', async (req, res) => {
  const zip = new JSZip();
  zip.file('data.txt', 'content');
  
  // Non-blocking calculation, but high memory usage
  const content = await zip.generateAsync({ type: 'nodebuffer' });
  res.send(content);
});

πŸ› οΈ Ease of Use and API Design

Sometimes you just need to zip a folder quickly for a build script or a small utility. Developer experience matters here.

adm-zip has a very direct, imperative API. You create an instance, add files, and write. It is extremely easy to read and write for simple tasks.

// adm-zip: Simple and direct
const zip = new AdmZip('existing.zip');
const entries = zip.getEntries();

entries.forEach(entry => {
  console.log(entry.entryName);
});

zip.extractAllTo('./output', true);

archiver requires understanding streams. You must pipe the archive to a destination and explicitly call finalize(). This adds a bit of boilerplate but gives you control.

// archiver: Stream-based setup
const archive = archiver('zip');
archive.on('error', err => { throw err; });
archive.pipe(fs.createWriteStream('output.zip'));
archive.file('input.txt', { name: 'renamed.txt' });
archive.finalize(); // Crucial step

jszip uses a chainable API that feels modern and clean, especially when dealing with async operations.

// jszip: Chainable and Promise-based
JSZip.loadAsync(fs.readFileSync('existing.zip'))
  .then(zip => {
    return zip.file('new.txt', 'content').generateAsync({ type: 'nodebuffer' });
  })
  .then(content => fs.writeFileSync('out.zip', content));

zip-local offers a concise API but lacks the rich ecosystem and documentation depth of the others.

// zip-local: Concise sync API
const zip = require('zip-local');
zip.sync.zip('file.txt').compress().save('out.zip');

πŸ” Similarities: Shared Ground

Despite their differences, these libraries share common goals and some overlapping capabilities.

1. Standard ZIP Format Support

All four libraries create standard .zip files that can be opened by Windows Explorer, macOS Archive Utility, and Linux unzip. They all support standard compression methods (Deflate) and directory structures.

// All produce valid ZIPs readable by OS tools
// adm-zip, archiver, jszip, zip-local -> archive.zip (Universal)

2. Basic File and Directory Addition

Each library allows you to add individual files and entire directories to an archive, though the method syntax varies.

// Conceptual equivalence across all
// adm-zip: zip.addLocalFile(), zip.addLocalFolder()
// archiver: archive.file(), archive.directory()
// jszip: zip.file(), zip.folder()
// zip-local: zip.sync.zip()

3. Extraction Capabilities

All packages provide mechanisms to extract contents from a ZIP file to the disk or memory.

// adm-zip: zip.extractAllTo(destination)
// archiver: (Requires unpack module or manual stream handling)
// jszip: zip.forEach((path, file) => ...)
// zip-local: zip.sync.unzip().extract(destination)

πŸ“Š Summary: Key Differences

Featurearchiverjszipadm-zipzip-local
Primary Model🌊 StreamingπŸ’Ύ In-MemoryπŸ’Ύ In-MemoryπŸ’Ύ In-Memory
EnvironmentNode.js OnlyNode + BrowserNode.js OnlyNode.js Only
Memory UsageLow (Chunked)High (Full Load)High (Full Load)High (Full Load)
Event LoopNon-BlockingAsync Gen / Blocking I/OBlockingBlocking
Best ForLarge Files / ServersFrontend / Full-StackScripts / Small FilesLegacy / Simple Tools

πŸ’‘ The Big Picture

Choosing the right library comes down to your specific constraints:

archiver is the professional choice for backend systems. If you are building an API that serves downloads, processes user uploads, or handles large datasets, archiver is the only safe bet. Its streaming architecture ensures your server stays responsive and memory-stable.

jszip is the bridge between client and server. If your feature requires zipping files in the browser (like exporting a project from a web IDE) or if you want to share logic between frontend and backend, jszip is indispensable.

adm-zip is the utility knife for scripts. For build tools, CLI commands, or internal admin scripts where you control the input size and concurrency is not an issue, its simplicity is a virtue. It gets the job done with minimal code.

zip-local remains a viable option for very specific, simple use cases, but for most new architectural decisions, adm-zip or archiver offers better long-term support and community resources.

Final Thought: Never use an in-memory library (adm-zip, zip-local, or jszip in Node) for large file operations on a public-facing server. The risk of crashing your application due to memory exhaustion is too high. Stick to streams (archiver) for production data pipelines.

How to Choose: adm-zip vs archiver vs jszip vs zip-local

  • adm-zip:

    Choose adm-zip when you need a quick, synchronous solution for manipulating small to medium-sized ZIP files entirely in memory. It is best suited for build scripts, CLI tools, or backend services where file sizes are predictable and won't exhaust server RAM. Avoid this package for large file transfers or high-concurrency environments because its blocking nature and memory usage can degrade application performance.

  • archiver:

    Choose archiver for production server-side applications that need to stream large files or entire directories directly to a response stream or disk. It is the architectural choice for high-scale systems because it processes data in chunks, preventing memory overflow and allowing the server to remain responsive during compression. Use this when your priority is throughput, scalability, and the ability to pipe data directly to HTTP responses or cloud storage.

  • jszip:

    Choose jszip if your application requires ZIP functionality in the web browser or needs a unified library that works identically on both client and server. It is the only viable option among these for frontend-heavy features like generating downloads directly in the browser or unpacking archives uploaded by users before processing them. Select this when cross-environment compatibility is more critical than raw server-side streaming performance.

  • zip-local:

    Choose zip-local only for legacy projects that already depend on it or for very simple, synchronous local file tasks where its specific API style is preferred. For new projects, it is generally recommended to evaluate adm-zip or archiver instead, as zip-local has less active maintenance and a smaller community ecosystem. It serves as a lightweight alternative for basic compression but lacks the robust streaming features of archiver or the browser support of jszip.

README for adm-zip

ADM-ZIP for NodeJS

ADM-ZIP is a pure JavaScript implementation for zip data compression for NodeJS.

Build Status

Installation

With npm do:

$ npm install adm-zip

Electron file system support described below.

What is it good for?

The library allows you to:

  • decompress zip files directly to disk or in memory buffers
  • compress files and store them to disk in .zip format or in compressed buffers
  • update content of/add new/delete files from an existing .zip

Dependencies

There are no other nodeJS libraries that ADM-ZIP is dependent of

Examples

Basic usage

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.

Electron original-fs

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

Security

Please report security vulnerabilities privately. See SECURITY.md.