I’m working on a Node.js + Express backend where I need to generate and download Excel reports from MongoDB using ExcelJS.
When the user selects a large date range (2-3 months) — around 60 MB of Excel data — the server times out with a 502 Gateway Timeout on AWS.
When I select a 1-month range, it works fine.
What I tried:
Initially, my code generated a single Excel file for the entire range:
downloadReportsExcel: async (req, res) => {
try {
req.setTimeout(0);
const { from, to } = req.query;
const fromDate = new Date(from);
const toDate = new Date(to);
const reports = await Report.find({
createdAt: { $gte: fromDate, $lte: toDate },
}).populate("case reportedBy");
const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: res });
const worksheet = workbook.addWorksheet("Reports");
worksheet.columns = [
{ header: "CONTRACTOR NAME", key: "contractorName", width: 25 },
// ...other columns
];
res.setHeader(
"Content-Disposition",
`attachment; filename="Reports_${from}_to_${to}.xlsx"`
);
for (const report of reports) {
worksheet.addRow({
"CONTRACTOR NAME": report.contractorName || "N/A",
// ...
}).commit();
}
worksheet.commit();
await workbook.commit();
} catch (err) {
console.error(err);
}
};
✅ This worked for smaller date ranges (1 month),
❌ But failed for larger ranges (2–3 months, ~60MB file) with 502 Gateway Timeout after 2–3 minutes (AWS default limit).
Attempted fix (split into monthly chunks and zip)
To fix it, I tried splitting the range into monthly chunks, generating separate Excel files for each month, and then zipping them together:
const chunks = getMonthlyChunks(fromDate, toDate);
const zip = new JSZip();
for (const chunk of chunks) {
const reports = await Report.find({
createdAt: { $gte: chunk.start, $lte: chunk.end },
});
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Reports");
worksheet.columns = [...];
for (const report of reports) {
worksheet.addRow({...});
}
const buffer = await workbook.xlsx.writeBuffer();
const chunkName = `Reports_${chunk.start.toISOString()}_${chunk.end.toISOString()}.xlsx`;
zip.file(chunkName, buffer);
}
const zipBuffer = await zip.generateAsync({ type: "nodebuffer" });
res.setHeader("Content-Type", "application/zip");
res.setHeader("Content-Disposition", `attachment; filename="Reports.zip"`);
res.send(zipBuffer);
✅ Works locally for 1-month data,
❌ Still times out for 2–3 months on AWS (file ~60 MB, 2–3 min processing).