Generate CSV with One-Time Download
import type { Handler } from "hono";
// POST /generate — create temp file
export const generateCsv: Handler = async (c: any) => {
const csv = ["id,title", "1,nexwire report", "2,temporary file"].join("\n");
const token = await storage.generateForDownload({
prefix: "report",
extension: "csv",
data: csv,
});
return c.json({
token,
downloadUrl: `/download/${encodeURIComponent(token)}`,
});
};
// GET /download/:token — serve once and delete
export const downloadCsv: Handler = async (c: any) => {
const token = decodeURIComponent(c.req.param("token"));
try {
const file = await storage.consumeGenerated(token);
return new Response(file, {
headers: {
"content-type": "text/csv; charset=utf-8",
"content-disposition": "attachment; filename=report.csv",
},
});
} catch {
return c.json({ message: "Download token expired or invalid" }, 404);
}
};import type { Request, Response } from "express";
// POST /generate — create temp file
export const generateCsv = async (req: Request, res: Response) => {
const csv = ["id,title", "1,nexwire report", "2,temporary file"].join("\n");
const token = await storage.generateForDownload({
prefix: "report",
extension: "csv",
data: csv,
});
res.json({
token,
downloadUrl: `/download/${encodeURIComponent(token)}`,
});
};
// GET /download/:token — serve once and delete
export const downloadCsv = async (req: Request, res: Response) => {
const token = decodeURIComponent(req.params.token);
try {
const file = await storage.consumeGenerated(token);
res
.set("content-type", "text/csv; charset=utf-8")
.set("content-disposition", "attachment; filename=report.csv")
.send(file);
} catch {
res.status(404).json({ message: "Download token expired or invalid" });
}
};
storage.consumeGenerated()returns aBufferin both engines. Hono sends it vianew Response(file, ...); Express sends it withres.send(file)after setting the content headers.
Generate Styled Excel
import type { Handler } from "hono";
export const generateExcel: Handler = async (c: any) => {
const ExcelJS = (await import("exceljs")).default;
const body = c.req.valid("json") as { title: string };
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet("Data");
// Title row
sheet.mergeCells("A1:C1");
sheet.getCell("A1").value = body.title;
sheet.getCell("A1").font = { bold: true, size: 14 };
// Header row
sheet.addRow(["ID", "Title", "Length"]);
sheet.getRow(2).font = { bold: true };
// Data rows
const rows = [
{ id: 1, title: "nexwire framework" },
{ id: 2, title: "file generation" },
];
for (const row of rows) {
sheet.addRow([row.id, row.title, row.title.length]);
}
// Totals
sheet.addRow([
"Subtotal",
rows.length,
{ formula: `SUM(C3:C${2 + rows.length})` },
]);
sheet.addRow([
"Grand Total",
rows.length,
{ formula: `SUM(C3:C${2 + rows.length})` },
]);
sheet.columns = [{ width: 10 }, { width: 38 }, { width: 14 }];
const buffer = Buffer.from(await workbook.xlsx.writeBuffer());
const token = await storage.generateForDownload({
prefix: "report",
extension: "xlsx",
data: buffer,
});
return c.json({
token,
downloadUrl: `/download/excel/${encodeURIComponent(token)}`,
});
};// modules/report/controllers/report-export.controller.ts (generate route)
import type { Request, Response } from "express";
export const generateExcel = async (req: Request, res: Response) => {
const ExcelJS = (await import("exceljs")).default;
const body = req.body as { title: string }; // validated in place
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet("Data");
// Title row
sheet.mergeCells("A1:C1");
sheet.getCell("A1").value = body.title;
sheet.getCell("A1").font = { bold: true, size: 14 };
// Header row
sheet.addRow(["ID", "Title", "Length"]);
sheet.getRow(2).font = { bold: true };
// Data rows
const rows = [
{ id: 1, title: "nexwire framework" },
{ id: 2, title: "file generation" },
];
for (const row of rows) {
sheet.addRow([row.id, row.title, row.title.length]);
}
// Totals
sheet.addRow([
"Subtotal",
rows.length,
{ formula: `SUM(C3:C${2 + rows.length})` },
]);
sheet.addRow([
"Grand Total",
rows.length,
{ formula: `SUM(C3:C${2 + rows.length})` },
]);
sheet.columns = [{ width: 10 }, { width: 38 }, { width: 14 }];
const buffer = Buffer.from(await workbook.xlsx.writeBuffer());
const token = await storage.generateForDownload({
prefix: "report",
extension: "xlsx",
data: buffer,
});
res.json({
token,
downloadUrl: `/download/excel/${encodeURIComponent(token)}`,
});
};Stream Excel for Very Large Data (generateForDownloadStream)
Why this exists:
generateForDownload({ data })andworkbook.xlsx.writeBuffer()hold the entire workbook in RAM. For very large exports that memory spike can OOM the process.generateForDownloadStreampipes an inboundReadableto a temp file with backpressure — memory stays bounded regardless of data size.
exceljs API note: exceljs exposes
write(target)(pipes the workbook into aWritable),writeBuffer()(returns the wholeBuffer— RAM heavy) andwriteFile(path)— butworkbook.xlsx.writeStream()does NOT exist. Do not emitworkbook.xlsx.writeStream(); use thePassThroughbridge below instead.The bridge pattern:
workbook.xlsx.write(pass)writes into the Writable side of aPassThrough, whilegenerateForDownloadStreamconsumes the Readable side. Backpressure flows through the bridge, so memory between exceljs and disk is capped at ~16KB.
import { storage } from "@/framework/facade.js";
import { PassThrough } from "node:stream";
import ExcelJS from "exceljs";
// POST /report/export - streaming Excel for very large data
export const exportReport: Handler = async (c: any) => {
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet("Report");
// ...fill the sheet row by row...
const bridge = new PassThrough();
const pendingToken = storage.generateForDownloadStream({
prefix: `report_${c.req.valid("param").id}`,
extension: "xlsx",
stream: bridge,
});
await workbook.xlsx.write(bridge);
const token = await pendingToken;
return c.json({
token,
downloadUrl: `/download/excel/${encodeURIComponent(token)}`,
});
};import { storage } from "@/framework/facade.js";
import { PassThrough } from "node:stream";
import ExcelJS from "exceljs";
// POST /report/export - streaming Excel for very large data
export const exportReport = async (req: Request, res: Response) => {
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet("Report");
// ...fill the sheet row by row...
const bridge = new PassThrough();
const pendingToken = storage.generateForDownloadStream({
prefix: `report_${req.params.id}`,
extension: "xlsx",
stream: bridge,
});
await workbook.xlsx.write(bridge);
const token = await pendingToken;
res.json({
token,
downloadUrl: `/download/excel/${encodeURIComponent(token)}`,
});
};Both engines resolve
pendingTokenonly after the piped stream reachesclose(file fully written). Keep the token pending before writing into the bridge — iterating rows first, thenawait workbook.xlsx.write(bridge)inflates the workbook fully in memory instead of streaming.Use
generateForDownloadStreamwhenever the data size is unknown / very large (100 MB+). UsegenerateForDownload({ data })only when you already hold a smallBufferin hand.
Generate PDF with Playwright (HTML → PDF)
import type { Handler } from "hono";
export const generatePdf: Handler = async (c: any) => {
const { chromium } = await import("playwright");
const body = c.req.valid("json") as { title: string; rows: number };
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setContent(buildHtml(body), { waitUntil: "networkidle" });
const pdf = await page.pdf({ format: "A4", printBackground: true });
const token = await storage.generateForDownload({
prefix: "report",
extension: "pdf",
data: pdf,
});
return c.json({
token,
downloadUrl: `/download/pdf/${encodeURIComponent(token)}`,
});
} finally {
await browser.close();
}
};// modules/report/controllers/report-export.controller.ts (generate PDF route)
import type { Request, Response } from "express";
export const generatePdf = async (req: Request, res: Response) => {
const { chromium } = await import("playwright");
const body = req.body as { title: string; rows: number }; // validated in place
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setContent(buildHtml(body), { waitUntil: "networkidle" });
const pdf = await page.pdf({ format: "A4", printBackground: true });
const token = await storage.generateForDownload({
prefix: "report",
extension: "pdf",
data: pdf,
});
res.json({
token,
downloadUrl: `/download/pdf/${encodeURIComponent(token)}`,
});
} finally {
await browser.close();
}
};Large PDF Generation (Queued + Status Polling)
For heavy reports, generate in a background worker and poll for the result:
import type { Handler } from "hono";
import crypto from "node:crypto";
// Controller: queue the work
export const generatePdfQueued: Handler = async (c: any) => {
const body = c.req.valid("json");
const requestId = crypto.randomUUID();
await cache.put(`pdf:status:${requestId}`, { state: "pending" }, 1800);
await dispatchCommand(
"report.pdf.generate",
{ requestId, ...body },
{
async: true,
queue: "default",
},
);
return c.json(
{
requestId,
statusUrl: `/pdf/${requestId}`,
downloadUrl: `/pdf/${requestId}`,
},
202,
);
};
// Controller: poll status, then serve
export const downloadPdf: Handler = async (c: any) => {
const requestId = c.req.param("requestId");
const status = await cache.get(`pdf:status:${requestId}`);
if (!status) return c.json({ message: "Unknown request" }, 404);
if (status.state === "pending") return c.json({ state: "pending" }, 202);
if (status.state === "failed")
return c.json({ message: status.message }, 500);
const file = await storage.consumeGenerated(status.token);
await cache.forget(`pdf:status:${requestId}`);
return new Response(file, {
headers: {
"content-type": "application/pdf",
"content-disposition": "attachment; filename=report.pdf",
},
});
};// modules/report/controllers/report-export.controller.ts
import type { Request, Response } from "express";
import crypto from "node:crypto";
// Controller: queue the work
export const generatePdfQueued = async (req: Request, res: Response) => {
const body = req.body; // validated in place
const requestId = crypto.randomUUID();
await cache.put(`pdf:status:${requestId}`, { state: "pending" }, 1800);
await dispatchCommand(
"report.pdf.generate",
{ requestId, ...body },
{
async: true,
queue: "default",
},
);
res.status(202).json({
requestId,
statusUrl: `/pdf/${requestId}`,
downloadUrl: `/pdf/${requestId}`,
});
};
// Controller: poll status, then serve
export const downloadPdf = async (req: Request, res: Response) => {
const requestId = req.params.requestId;
const status = await cache.get(`pdf:status:${requestId}`);
if (!status) return res.status(404).json({ message: "Unknown request" });
if (status.state === "pending")
return res.status(202).json({ state: "pending" });
if (status.state === "failed")
return res.status(500).json({ message: status.message });
const file = await storage.consumeGenerated(status.token);
await cache.forget(`pdf:status:${requestId}`);
res
.status(200)
.set("content-type", "application/pdf")
.set("content-disposition", "attachment; filename=report.pdf")
.send(file);
};// Worker
shouldQueue("report.pdf.generate", "default", async (job) => {
const { requestId, title, rows } = job.data;
try {
const token = await generatePdfToken({ title, rows });
await cache.put(`pdf:status:${requestId}`, { state: "ready", token }, 1800);
} catch (error) {
await cache.put(
`pdf:status:${requestId}`,
{ state: "failed", message: String(error) },
1800,
);
}
});