Streaming and Files
Stream binary data, pipe readable streams, return Blob/Buffer payloads, and serve local files with path traversal security.
Taser.js includes first-class helpers for streaming responses, binary payloads, and secure file downloads without manual buffer management.
Serving Local Files (file)
Use file() imported from @taserjs/router/stream to serve files from disk. It automatically looks up the correct MIME type based on file extension and streams the file using Node.js read streams:
import { file } from "@taserjs/router/stream";
import { z } from "zod";
import { t } from "@taserjs/router";
import path from "node:path";
export default t
.get("/downloads/:file")
.params(z.object({ file: z.string() }))
.handler((ctx) => {
const publicStorageDir = path.resolve(process.cwd(), "storage/public");
// Serves the requested file safely constrained to publicStorageDir:
return file(ctx.params.file, {
root: publicStorageDir,
});
});Path Traversal Protection
When root is specified, Taser.js validates that the resolved file path does not escape the
boundary directory, protecting your application against ../ directory traversal attacks.
Piping Readable Streams (pipe)
To stream dynamically generated data, AI completions, or proxy upstream streams, use pipe():
import { pipe } from "@taserjs/router/stream";
import { t } from "@taserjs/router";
export default t.post("/ai/generate").handler(async (ctx) => {
const encoder = new TextEncoder();
const customStream = new ReadableStream({
async start(controller) {
for (const word of ["Streaming", " live", " AI", " response", "..."]) {
controller.enqueue(encoder.encode(word));
await new Promise((resolve) => setTimeout(resolve, 200));
}
controller.close();
},
});
return pipe(customStream, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Transfer-Encoding": "chunked",
},
});
});Binary Payloads (buffer and blob)
When returning generated images, PDFs, or ZIP archives:
import { buffer } from "@taserjs/router/stream";
import { t } from "@taserjs/router";
export default t.get("/export/pdf").handler(async (ctx) => {
const pdfBytes: Uint8Array = await generateInvoicePdf(ctx.query);
return buffer(pdfBytes, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": 'attachment; filename="invoice.pdf"',
},
});
});Server-Sent Events (SSE)
You can build Server-Sent Events (SSE) streams by piping formatted SSE message events:
import { stream } from "@taserjs/router/stream";
import { t } from "@taserjs/router";
export default t.get("/events/notifications").handler((ctx) => {
const encoder = new TextEncoder();
const eventStream = new ReadableStream({
start(controller) {
const interval = setInterval(() => {
const payload = `data: ${JSON.stringify({ time: new Date().toISOString() })}\n\n`;
controller.enqueue(encoder.encode(payload));
}, 1000);
ctx.request.signal.addEventListener("abort", () => {
clearInterval(interval);
controller.close();
});
},
});
return stream.pipe(eventStream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
});Next Steps
Response Contracts
Enforce compile-time return shape safety and runtime response validation with .returns(). Eliminate response drift between backend and frontend.
Global Error Handling
Catch unhandled exceptions and route misses centrally using .onError() and .notFound(). Standardize error envelopes across your entire API.