# [Node.js]ファイルをZIP圧縮してダウンロードする方法 - [\[Node.js\]ファイルをZIP圧縮してダウンロードする方法](#nodejsファイルをzip圧縮してダウンロードする方法) - [圧縮処理について](#圧縮処理について) - [方法1:フロンサイドで圧縮データを処理しファイルを保存する(JSZip)](#方法1フロンサイドで圧縮データを処理しファイルを保存するjszip) - [方法2:サーバサイドで圧縮処理しファイルを出力する](#方法2サーバサイドで圧縮処理しファイルを出力する) - [方法3:サーバサイドで圧縮処理しフロントでBlobによりファイルを取得する](#方法3サーバサイドで圧縮処理しフロントでblobによりファイルを取得する) - [サンプルソース](#サンプルソース) - [Blobを使用してZIPファイルを処理する(フロント側の処理)\*\*](#blobを使用してzipファイルを処理するフロント側の処理) - [gcloudにあるファイルをzip形式でファイルを出力するサンプルソース](#gcloudにあるファイルをzip形式でファイルを出力するサンプルソース) - [Zipダウンロードサーバを構築する(gcloud)](#zipダウンロードサーバを構築するgcloud) ## 圧縮処理について * 方法1:フロンサイドで圧縮データを処理しファイルを保存する(JSZip) * 方法2:サーバサイドで圧縮処理しファイルを出力する * 方法3:サーバサイドで圧縮処理しフロントでBlobによりファイルを取得する ### 方法1:フロンサイドで圧縮データを処理しファイルを保存する(JSZip) * **ファイル量が多い場合(30MB以上など)はサーバーサイドで処理するほうがいい** * 大容量データの処理には向かない * ユーザーのデバイスの性能に依存する ```mermaid sequenceDiagram User->>Front: ダウンロードボタンを要求する loop ファイル毎に処理する Front->>Storage: ファイルを要求する(fetch) Storage-->>Front: ファイル情報を返す end Front-->>Front: Zip処理を実行する(JSZip) Front-->>User: ダウンロード処理を実行する(blob) ``` * https://stuk.github.io/jszip/documentation/examples.html * [サンプルコード](../src/front/assets/download.js) ``` import JSZip from "jszip"; ``` **CDNでインポートする場合** ```js ``` --- ### 方法2:サーバサイドで圧縮処理しファイルを出力する サーバーサイドでarchiverを使ってZIPを作成し、 一時ファイルとして保存してからダウンロード用のURLを返します * archiverモジュールで実行する * Zipファイルの保存のためにストレージが別で必要 * ライフサイクルを考慮しないと不要なファイルが溜まる * 大容量データの圧縮に適してい * 複数のリクエストに対応しやすい ```mermaid sequenceDiagram User->>Front: ダウンロードボタンを要求する Front->>Server: ダウンロードを要求する Server->>Storage: ファイルデータ取得API Storage->>Server: レスポンス Server->>Server: Zip処理を実行する(archiver) Server->>Storage: 作成した Zipを保存する Storage->>Server: レスポンス Server-->>Front: ファイルのURLを返す Front->>Storage: URLをリンクする(HTTP) Storage-->>User: ファイルをダウンロードする ``` ```sh npm install archiver ``` --- ### 方法3:サーバサイドで圧縮処理しフロントでBlobによりファイルを取得する ZIPを一時ファイルとして保存せずに 直接フロントエンドにストリームとして送信します。 * archiverモジュールで実行する * 保存せずにファイルコンテンツを返す * リアルタイムでファイルをストリーミング可能 * ストレージの管理が不要 * クライアントがダウンロードを途中でキャンセルするとデータが失われる可能性がある ```mermaid sequenceDiagram User->>Front: ダウンロードボタンを要求する Front->>Server: ダウンロードを要求する Server->>Storage: ファイルデータ取得API Storage->>Server: レスポンス Server->>Server: Zip処理を実行する(archiver) Storage->>Front: Zipされたファイルコンテンツを返す Front-->>User: ダウンロード処理を実行する(blob) ``` ## サンプルソース ### Blobを使用してZIPファイルを処理する(フロント側の処理)** ```js fetch("https://example.com/sample.zip") .then(response => response.blob()) .then(blob => { const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "downloaded.zip"; document.body.appendChild(a); a.click(); URL.revokeObjectURL(url); }) .catch(error => console.error("Error downloading ZIP:", error)); ``` #### gcloudにあるファイルをzip形式でファイルを出力するサンプルソース (スクリプト) ```js const { Storage } = require('@google-cloud/storage'); const archiver = require('archiver'); const fs = require('fs'); const storage = new Storage(); const bucketName = 'your-bucket-name'; const filesToDownload = ['file1.csv', 'file2.csv']; // 圧縮したいファイルリスト const outputZip = 'output.zip'; async function downloadAndZip() { const output = fs.createWriteStream(outputZip); const archive = archiver('zip', { zlib: { level: 9 } }); output.on('close', () => { console.log(`Zip file created: ${outputZip} (${archive.pointer()} bytes)`); }); archive.pipe(output); for (const fileName of filesToDownload) { const file = storage.bucket(bucketName).file(fileName); const [exists] = await file.exists(); if (exists) { console.log(`Adding ${fileName} to archive...`); archive.append(file.createReadStream(), { name: fileName }); } else { console.warn(`File not found: ${fileName}`); } } archive.finalize(); } downloadAndZip().catch(console.error); ``` #### Zipダウンロードサーバを構築する(gcloud) (サーバ) ```js /** * @fileoverview Google Cloud Storage (GCS) download module. */ const { Storage } = require('@google-cloud/storage'); const archiver = require('archiver'); const KEY_FILE_PATH = './keys/service-account.json' const storage = new Storage({ keyFilename: KEY_FILE_PATH}); // Load environment variables require('dotenv').config(); // バケット名を.envから取得する const BUCKET_NAME = process.env.BUCKET_NAME; console.log(`BUCKET_NAME: ${BUCKET_NAME}`); /** * GCStorageからファイルをダウンロードする * * @param {http.IncomingMessage} req * @param {http.ServerResponse} res */ const downloadFilesFromGCS = async (req, res) => { // バケットからファイル一覧を取得する const [files] = await storage.bucket(BUCKET_NAME).getFiles(); const filesToZip = files.map((file) => file.name); res.setHeader('Content-Disposition', 'attachment; filename="files.zip"'); res.setHeader('Content-Type', 'application/zip'); const archive = archiver('zip', { zlib: { level: 9 } }); archive.on('error', (err) => res.status(500).send({ error: err.message })); archive.pipe(res); for (const fileName of filesToZip) { const file = storage.bucket(BUCKET_NAME).file(fileName); archive.append(file.createReadStream(), { name: fileName }); } archive.finalize(); }; module.exports = downloadFilesFromGCS; ```