Minimal, single-file library for generating ZIP archives with Node.js streams and a Promise-based API
- Zero dependencies except Node.js built-in modules
- Implements a Node.js
Readablestream - Supported compression methods: Store (no compression), Deflate
- Configurable with zlib options (level, chunk size, etc.) for Deflate
- UTF-8 filenames enforced
Not supported:
- ZIP64 (files >= 4 GiB, etc.)
- Splitting or spanning archives
- Configuring file attributes
- Comments
import { Buffer } from 'node:buffer';
import { writeFile } from 'node:fs/promises';
import { constants } from 'node:zlib';
import { ZipStream, dosDateTimeFrom } from 'mozip';
const zip = new ZipStream();
const finished = writeFile('example.zip', zip).catch((error) => {
console.error(error); // Stream or file writing error
});
// Files are added in `appendFile()` call order, though compression may run in parallel.
// No need to await this unless parallel task management is needed.
await zip.appendFile('heavy/file', Buffer.alloc(2**31 - 1));
const data = Buffer.from('Hello, world!\n'.repeat(2**16));
zip.appendFile('compressed', data);
zip.appendFile('stored', data, { compress: false });
zip.appendFile('compressed/best', data, {
zlib: { level: constants.Z_BEST_COMPRESSION },
});
const date = new Date('2000-01-02T01:23:45.678Z');
zip.appendFile('timezone/local', data, { lastModified: date });
zip.appendFile('timezone/UTC+9', data, {
lastModified: dosDateTimeFrom(date.getTime(), 9 * 60 * 60 * 1000),
});
// Rejects if a filename starts with a drive letter.
zip.appendFile('C:\\file', data).catch(() => {
console.log('Skipped; the stream is still alive.');
});
// Must call `finalize()` after all files are added.
// No need to await this unless the total size is needed.
zip.finalize().then((byteLength) => {
if (byteLength < 0) return; // Failed to finalize
console.log(`Total size of the ZIP archive: ${byteLength} bytes`);
});
await finished;Install mozip from the npm registry with your preferred package manager, such as:
npm install mozipThen import the package:
import { ZipStream, dosDateTimeFrom } from 'mozip';Alternatively, download mozip.js from the latest release and import it directly.
In Node.js, v22.2+ is required. Using the package in CommonJS modules requires v22.10+.
Also compatible with any runtime providing:
Bufferfromnode:bufferReadablefromnode:streampromisify()fromnode:utilcrc32(),deflateRaw()fromnode:zlib- Global
Promise.withResolvers()
- Extends:
Readablefromnode:stream [options]:{Object}Passed to theReadableconstructor.
Can be piped or passed to any consumer that accepts a Readable, such as writeFile() from node:fs/promises.
Stream errors are always emitted by the stream itself, never rejected within instance method calls.
Instance fields not documented here should not be considered public API.
name:{string}Filename- Returns:
{string}Normalized filename
Normalizes and validates a filename according to the minimum ZIP requirements. Removes leading slashes, throws an error if the name starts with a drive letter, and replaces backward slashes with forward slashes.
Can be overridden to enforce stricter rules, such as EPUB restrictions.
name:{string}Filenamedata:{TypedArray | DataView}File data[options]:{Object}[compress]:{boolean}Deflate if true, store if false. Defaults to true.[lastModified]:{Date | number}Last modified date/time of the file, defaults to the current local time. If an unsigned 32-bit integer, it is interpreted as MS-DOS date and time combined from high to low, as produced bydosDateTimeFrom().[zlib]:{node:zlib.Options}Options for deflate compression. Implements theOptionsinterface fromnode:zlib.
- Returns:
{Promise<boolean>}Fulfills with true once the file header and data have been pushed to the internal read buffer, or false if the stream is destroyed while processing.
Adds a file to the ZIP archive. Files are added in call order, though compression may run in parallel.
Do not modify the contents of data after passing it; the view is compressed or written directly without being copied.
Even when options.compress is true (the default), the file is stored if compressing did not reduce its size.
To set a specific time zone for options.lastModified, pass a value produced by dosDateTimeFrom(). By default, the local offset at the timestamp is used.
Rejected only before stream writing with:
Errorif the stream has been destroyed, orfinalize()was already called.RangeErrorif 65535 (0xFFFF) files have already been added.TypeErrorif any parameter has an invalid type.Errorif the filename starts with a drive letter.RangeErrorif the filename length in UTF-8 bytes reaches or exceeds 64 KiB, or the file size reaches or exceeds 4 GiB.RangeErrorif the archive size before the central directory or the central directory size would reach or exceed 4 GiB.
Errors during writing are emitted by the stream.
- Returns:
{Promise<number>}Fulfills with the total byte size of the archive, or -1 if the stream is destroyed while processing.
Finalizes the ZIP archive by writing the central directory and ending the stream. Must be called after all files are added.
Rejected with an Error if the stream has been destroyed, finalize() was already called, or no files have been added.
If every file failed to be added, the stream is destroyed and an Error is emitted by the stream.
This does not wait for the stream to be completely consumed. To await complete stream consumption, finished() from node:stream/promises may be useful:
import { finished } from 'node:stream/promises';
import { ZipStream } from 'mozip';
/* ... */
const zip = new ZipStream();
const consumed = finished(zip);
/* Pipe the stream, call `zip.appendFile()` more than once, etc. */
zip.finalize();
try {
await consumed;
console.log('Stream consumed');
} catch (error) {
console.error('Stream errored', error);
}epochMilliseconds:{number}Milliseconds since the epoch (1970-01-01T00:00:00Z)[offsetMilliseconds]:{number}UTC offset in milliseconds, defaults to the local time zone offset atepochMilliseconds- Returns:
{number}Unsigned 32-bit integer combining MS-DOS date and time from high to low. Clamped to the MS-DOS date range of 1980 to 2107.
Used with the options.lastModified parameter of ZipStream#appendFile(). Useful for applying the same timestamp to multiple files, or setting a specific time zone.
Note: The sign of Date#getTimezoneOffset() is opposite to that of the UTC offset.
The name Mozip is a pun; the Korean word 모집 (mo-jip) means gathering or collecting, and zip is written 집 in Hangul.