File Serving / Download / Export / Archive#
Helper functions for serving files, file downloads, data export, and archive creation. Zero dependencies — uses Bun.Archive (native tar) for archives.
typescript
import {
serveFileOrFallback,
downloadFile, downloadBuffer,
exportCSV, exportJSON,
createZIP,
} from "@buntok/core";serveFileOrFallback#
Serve a file from disk. If file doesn't exist, return the fallback response. Useful for serving user uploads with a default fallback (e.g., avatar with initials).
| Parameter | Type | Description |
|---|---|---|
| ctx | Context | Request context |
| filePath | string | Path to the file on disk |
| fallback | Response | (() => Response | Promise<Response>) | Response to return if file not found |
| options? | { contentType?, cacheControl? } | Response options |
typescript
import { serveFileOrFallback, generateInitialAvatar } from "@buntok/core";
// Serve file or return 404
app.get("/documents/:id", async (ctx) => {
return serveFileOrFallback(ctx, doc.file_path, () => {
return ctx.json({ error: "Not found" }, 404);
});
});
// Serve avatar or return default SVG initials
app.get("/avatars/:userId", async (ctx) => {
const user = await db.users.find(ctx.params.userId);
return serveFileOrFallback(ctx, user.avatar_path, () => {
return new Response(generateInitialAvatar(user.name, user.id), {
headers: { "Content-Type": "image/svg+xml" }
});
});
});
// With custom cache control
app.get("/images/:name", async (ctx) => {
return serveFileOrFallback(ctx, `./uploads/${ctx.params.name}`, () => {
return new Response("Not found", { status: 404 });
}, { cacheControl: "public, max-age=3600" });
});Info
File is served with inline content disposition (displayed in browser). Use
downloadFile for forced downloads with attachment disposition.downloadFile#
Serve a file for download with Content-Disposition: attachment header.
| Parameter | Type | Description |
|---|---|---|
| ctx | Context | Request context |
| filePath | string | Path to the file |
| filename? | string | Download filename (defaults to basename) |
| options? | { contentType?, cacheControl? } | Response options |
typescript
import { downloadFile } from "@buntok/core";
// Serve file as download
app.get("/reports/:id", async (ctx) => {
return downloadFile(ctx, `./reports/${ctx.params.id}.pdf`);
});
// Custom filename
app.get("/export/users", async (ctx) => {
return downloadFile(ctx, "./data/users.csv", "users-export.csv");
});
// With options
app.get("/download/:filename", async (ctx) => {
return downloadFile(ctx, `./files/${ctx.params.filename}`, undefined, {
cacheControl: "no-cache",
});
});Info
Returns 404 if file not found. Content type is auto-detected via
Bun.file().downloadBuffer#
Download a buffer or bytes as a file.
| Parameter | Type | Description |
|---|---|---|
| ctx | Context | Request context |
| data | ArrayBuffer | Uint8Array | Blob | File data to download |
| filename | string | Download filename |
| options? | { contentType? } | Response options |
typescript
import { downloadBuffer } from "@buntok/core";
// Generate PDF on the fly
app.get("/generate-pdf", async (ctx) => {
const pdfBytes = await generatePDF(data);
return downloadBuffer(ctx, pdfBytes, "document.pdf");
});
// From Uint8Array
app.get("/download-binary", async (ctx) => {
const buffer = new Uint8Array([72, 101, 108, 108, 111]);
return downloadBuffer(ctx, buffer, "data.bin", {
contentType: "application/octet-stream",
});
});exportCSV#
Export array of objects as CSV file download. Handles commas, quotes, and newlines (RFC 4180 compliant).
| Parameter | Type | Description |
|---|---|---|
| ctx | Context | Request context |
| data | T[] | Array of objects to export |
| filename? | string | Download filename (default: export.csv) |
| options? | { delimiter?, header? } | CSV options |
typescript
import { exportCSV } from "@buntok/core";
// Export users as CSV
app.get("/users/export", async (ctx) => {
const users = await db.users.find();
return exportCSV(ctx, users, "users.csv");
});
// Custom delimiter
app.get("/orders/export", async (ctx) => {
const orders = await db.orders.find();
return exportCSV(ctx, orders, "orders.tsv", { delimiter: "\t" });
});
// Without header row
app.get("/data/export", async (ctx) => {
return exportCSV(ctx, rows, "data.csv", { header: false });
});Info
Auto-escapes values containing commas, quotes, or newlines. Nested objects are flattened with dot notation (e.g.,
user.name).exportJSON#
Export data as JSON file download.
typescript
import { exportJSON } from "@buntok/core";
app.get("/data/export", async (ctx) => {
const data = await db.orders.find();
return exportJSON(ctx, data, "orders.json");
});createZIP#
Create a tar archive from multiple files and serve as download. Uses Bun.Archive (native).
| Parameter | Type | Description |
|---|---|---|
| ctx | Context | Request context |
| files | ZIPEntry[] | Files to include ({ name, data }) |
| filename? | string | Download filename (default: archive.tar) |
| options? | { compress? } | Enable gzip compression |
typescript
import { createZIP, exportCSV, exportJSON } from "@buntok/core";
// Export multiple files as archive
app.get("/export/bundle", async (ctx) => {
const users = await db.users.find();
const orders = await db.orders.find();
return createZIP(ctx, [
{ name: "users.csv", data: exportCSVToString(users) },
{ name: "orders.json", data: JSON.stringify(orders) },
], "export.tar");
});
// With gzip compression
app.get("/export/compressed", async (ctx) => {
const data = await db.exports.find();
return createZIP(ctx, [
{ name: "data.json", data: JSON.stringify(data) },
], "export.tar.gz", { compress: true });
});Info
Uses tar format (not zip). For
.tar.gz, pass { compress: true }.