An isomorphic TypeScript package for downloading multiple files and combining them into a ZIP archive. Works in both browser and Node.js environments!
By Diego Alto | GitHub | NPM | Documentation | Live Demo
- ⚡ Isomorphic: Works in both browser and Node.js environments
- 📦 TypeScript Support: Full type definitions included
- 🚀 Concurrent Downloads: Download multiple files in parallel
- 📊 Progress Tracking: Built-in progress callbacks
- 🛡️ Error Handling: Robust error handling with optional error callbacks
- 🌐 Modern Dependencies: Uses standard npm packages (JSZip, File-Saver)
- 🔧 Flexible: Continue on errors or fail fast
- ⚙️ Customizable: Custom fetch options, timeouts, and ZIP filename
npm install @diegoaltoworks/zipperThat's it! File-saver is included automatically for browser support.
- Browser: Auto-triggers ZIP download
- Node.js: Returns Buffer for server-side use
- Next.js: Works in both Client Components and API Routes
- Express: Perfect for server-side ZIP generation
import { downloadZipFile, type FileInput } from '@diegoaltoworks/zipper';
const files: FileInput[] = [
{ url: '/api/documents/report1.pdf', name: 'Report 1.pdf' },
{ url: '/api/documents/report2.pdf', name: 'Report 2.pdf' },
];
await downloadZipFile(files, { zipFilename: 'reports.zip' });
// ✓ Triggers browser download automaticallyimport { createZipFile } from '@diegoaltoworks/zipper';
import { writeFile } from 'fs/promises';
const files = [
{ url: 'https://example.com/file1.pdf', name: 'Document A.pdf' },
{ url: 'https://example.com/file2.pdf', name: 'Document B.pdf' }
];
const buffer = await createZipFile(files);
// ✓ Returns Buffer in Node.js
await writeFile('output.zip', buffer);import zipper from '@diegoaltoworks/zipper';
// Same as downloadZipFile
await zipper(files, { zipFilename: 'download.zip' });// app/api/download/route.ts
import { NextResponse } from 'next/server';
import { createZipFile } from '@diegoaltoworks/zipper';
export async function GET() {
const buffer = await createZipFile([
{ url: 'https://example.com/file1.pdf', name: 'file1.pdf' },
{ url: 'https://example.com/file2.pdf', name: 'file2.pdf' }
]);
return new NextResponse(buffer, {
headers: {
'Content-Type': 'application/zip',
'Content-Disposition': 'attachment; filename="download.zip"'
}
});
}import { downloadZipFile, type FileInput, type DownloadOptions } from '@diegoaltoworks/zipper';
const files: FileInput[] = [
{ url: 'https://example.com/file1.pdf', name: 'Document A.pdf' },
{ url: 'https://example.com/file2.pdf', name: 'Document B.pdf' }
];
const options: DownloadOptions = {
zipFilename: 'my-documents.zip',
onProgress: (current, total) => {
console.log(`Downloaded ${current} of ${total} files`);
// Update your UI progress indicator here
},
onError: (error, file) => {
console.error(`Failed to download ${file.name}:`, error.message);
// Handle individual file errors
},
continueOnError: true, // Continue downloading even if some files fail
timeout: 30000, // 30 second timeout per file
fetchOptions: {
headers: {
'Authorization': 'Bearer your-token-here'
}
}
};
await downloadZipFile(files, options);import { useState } from 'react';
import { downloadZipFile, type FileInput } from '@diegoaltoworks/zipper';
function DownloadButton() {
const [progress, setProgress] = useState('');
const [isDownloading, setIsDownloading] = useState(false);
const handleDownload = async () => {
setIsDownloading(true);
const files: FileInput[] = [
{ url: '/api/file1.pdf', name: 'file1.pdf' },
{ url: '/api/file2.pdf', name: 'file2.pdf' }
];
try {
await downloadZipFile(files, {
zipFilename: 'files.zip',
onProgress: (current, total) => {
setProgress(`${current}/${total}`);
}
});
setProgress('Complete!');
} catch (error) {
console.error('Download failed:', error);
} finally {
setIsDownloading(false);
}
};
return (
<div>
<button onClick={handleDownload} disabled={isDownloading}>
{isDownloading ? `Downloading... ${progress}` : 'Download ZIP'}
</button>
</div>
);
}Downloads multiple files and creates a ZIP archive (works in both browser and Node.js).
-
filesFileInput[]- Array of files to downloadurlstring- The URL to fetch the file fromnamestring- The filename to use in the ZIP archive
-
options?DownloadOptions- Optional configurationonProgress?(current: number, total: number) => void- Progress callbackonError?(error: Error, file: FileInput) => void- Error callbackcontinueOnError?boolean- Continue on errors (default:true)timeout?number- Request timeout in milliseconds (default:30000)fetchOptions?RequestInit- Additional fetch options (headers, etc.)
Promise<Buffer>in Node.jsPromise<Blob>in browser
- Throws an error if no files are provided
- Throws an error if all downloads fail
- Throws an error if
continueOnErrorisfalseand any download fails
Downloads multiple files, creates a ZIP, and triggers browser download.
filesFileInput[]- Array of files to downloadoptions?DownloadOptions- Optional configurationzipFilename?string- Name of the ZIP file (default:'download.zip')- All options from
createZipFileare also supported
Promise<void> - Resolves when download is triggered
- Same errors as
createZipFile - Throws an error if browser is not supported
interface FileInput {
url: string;
name: string;
}
interface DownloadOptions {
zipFilename?: string;
onProgress?: (current: number, total: number) => void;
onError?: (error: Error, file: FileInput) => void;
continueOnError?: boolean;
timeout?: number;
fetchOptions?: RequestInit;
}Try the full-stack demo application at zipper-demo.vercel.app featuring:
- Interactive file selection and downloads
- Server-side ZIP generation with Next.js API routes
- Real-time progress tracking
- Error handling demonstrations
- Mobile-responsive UI
Source code: github.com/diegoaltoworks/zipper-demo
Browse examples and code samples at diegoaltoworks.github.io/zipper
The documentation site (docs/ directory) includes:
- Browser usage examples with live demos
- Server-side usage patterns
- Code playground for experimentation
- Multiple file type demonstrations
- 100+ test files for testing (PDFs, PNGs, text files)
To run the docs locally:
cd docs
npm install
npm run devnpm installnpm run buildOutputs ESM and CJS modules to dist/:
dist/index.esm.js- ES moduledist/index.cjs.js- CommonJS moduledist/index.d.ts- TypeScript definitions
npm test # Run tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage reportnpm run lint # Check for issues
npm run lint:fix # Auto-fix issues
npm run format # Format with Prettiernpm run type-checkThis package works in all modern browsers that support:
- Fetch API
- Blob
- Promise
- AbortController
For older browser support, you may need polyfills.
MIT
Built with:
- JSZip - Create ZIP files
- FileSaver.js - Save files in the browser
Contributions are welcome! Please feel free to submit a Pull Request.