File upload with FormData and Bun.serve
To upload files over HTTP with Bun, use the FormData API. Create an HTTP server with Bun.serve that serves an HTML form with enctype="multipart/form-data". The form sends a POST request with form data to an endpoint. On the server, call .formData() on the incoming Request to asynchronously parse its contents into a FormData instance. Use .get() to extract field values. File fields are Blob objects. Write the Blob to disk using Bun.write().
Bun.write() with Blob
Bun.write() can write a Blob object to disk. The first argument is the file path as a string, and the second argument is the Blob to write.
File upload example with Bun.serve
Example showing file upload handling with Bun.serve:
```ts
const server = Bun.serve({
port: 4000,
async fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/")
return new Response(Bun.file("index.html"), {
headers: {
"Content-Type": "text/html",
},
});
if (url.pathname === "/action") {
const formdata = await req.formData();
const name = formdata.get("name");
const profilePicture = formdata.get("profilePicture");
if (!profilePicture) throw new Error("Must upload a profile picture.");
await Bun.write("profilePicture.png", profilePicture);
return new Response("Success");
}
return new Response("Not Found", { status: 404 });
},
});
```
This example demonstrates parsing FormData from a POST request and writing an uploaded file to disk.
File I/O APIs
Bun provides the following file I/O APIs: Bun.file() for reading files, Bun.write() for writing files, Bun.stdin for standard input, Bun.stdout for standard output, and Bun.stderr for standard error.
Writing Archive to disk with Bun.write
Use Bun.write() to write an archive to disk. Pass the archive object as the second argument. Example: await Bun.write('output.tar', archive) for uncompressed tar or await Bun.write('output.tar.gz', compressed) for gzipped tar.
Bun.file() creates BunFile instances
Bun.file(path) returns a BunFile instance that represents a lazily-loaded file. The path can be a string relative to cwd, a file descriptor (number), or a file:// URL. Initializing a BunFile does not read the file from disk.
BunFile properties: size and type
A BunFile has two properties: size (number of bytes) and type (MIME type). The default MIME type is 'text/plain;charset=utf-8'. Override the type by passing a second argument to Bun.file() with an options object containing a type property.
BunFile read methods
BunFile conforms to the Blob interface and supports: text() returns Promise<string>, json() returns Promise<any>, arrayBuffer() returns Promise<ArrayBuffer>, bytes() returns Promise<Uint8Array>, and stream() returns ReadableStream.
BunFile.exists() and delete() methods
BunFile provides exists() which returns Promise<boolean> to check if a file exists, and delete() which returns Promise<void> to delete a file. A BunFile can point to a location where a file does not exist.
Bun.stdin, stdout, stderr standard streams
Bun exposes stdin, stdout, and stderr as BunFile instances. stdin is readonly, while stdout and stderr are writable.
Bun.write() function signature and parameters
Bun.write(destination, data) returns Promise<number>. destination can be a string path, URL (file://), or BunFile. data can be string, Blob (including BunFile), ArrayBuffer, SharedArrayBuffer, TypedArray (Uint8Array, et al.), or Response.
Bun.write() uses platform-optimized system calls
Bun.write() handles each combination of output and input with the fastest available system call on the current platform. On Linux it uses copy_file_range, sendfile, or splice depending on the input/output types. On macOS it uses clonefile or fcopyfile for files, and write for Blob/string.
FileSink incremental writing API
BunFile.writer(params?) returns a FileSink for incremental file writing. FileSink has methods: write(chunk) returns number, flush() returns number | Promise<number>, end(error?) returns number | Promise<number>, start(options?) with optional highWaterMark, ref(), and unref().
FileSink highWaterMark configuration
FileSink buffers chunks internally and auto-flushes when the high water mark is reached. Configure the high water mark by passing { highWaterMark: bytes } to the writer() method or start() method. Default behavior keeps the bun process alive until FileSink is closed with end().
FileSink ref() and unref() methods
FileSink.unref() opts out of the default behavior that keeps the bun process alive. FileSink.ref() can be used to 're-ref' an unreffed FileSink later.
Read directory with node:fs readdir
Use readdir from 'node:fs/promises' to read directories in Bun. Pass { recursive: true } option to readdir() to recursively read all files in a directory and subdirectories.
Create directory with node:fs mkdir
Use mkdir from 'node:fs/promises' to create directories. Pass { recursive: true } option to mkdir() to recursively create all parent directories.
Example: Bun.file() basic usage
const foo = Bun.file("foo.txt");
foo.size; // number of bytes
foo.type; // MIME type
Example: Reading file contents
const foo = Bun.file("foo.txt");
await foo.text(); // contents as a string
await foo.json(); // contents as a JSON object
foo.stream(); // contents as ReadableStream
await foo.arrayBuffer(); // contents as ArrayBuffer
await foo.bytes(); // contents as Uint8Array
Example: BunFile from descriptor or URL
Bun.file(1234); // from file descriptor
Bun.file(new URL(import.meta.url)); // reference to the current file
Example: Override BunFile MIME type
const notreal = Bun.file("notreal.json", { type: "application/json" });
notreal.type; // => "application/json;charset=utf-8"
Example: Check if file exists and delete
const notreal = Bun.file("notreal.txt");
const exists = await notreal.exists(); // false
await Bun.file("logs.json").delete();
Example: Bun.write() string to disk
const data = `It was the best of times, it was the worst of times.`;
await Bun.write("output.txt", data);
Example: Bun.write() copy file
const input = Bun.file("input.txt");
const output = Bun.file("output.txt"); // doesn't exist yet!
await Bun.write(output, input);
Example: Bun.write() byte array
const encoder = new TextEncoder();
const data = encoder.encode("datadatadata"); // Uint8Array
await Bun.write("output.txt", data);
Example: Bun.write() to stdout
const input = Bun.file("input.txt");
await Bun.write(Bun.stdout, input);
Example: Bun.write() HTTP response
const response = await fetch("https://bun.com");
await Bun.write("index.html", response);
Example: FileSink incremental writing
const file = Bun.file("output.txt");
const writer = file.writer();
writer.write("it was the best of times\n");
writer.write("it was the worst of times\n");
writer.flush(); // write buffer to disk
writer.end(); // flush and close
Example: FileSink with custom highWaterMark
const file = Bun.file("output.txt");
const writer = file.writer({ highWaterMark: 1024 * 1024 }); // 1MB
Example: FileSink ref and unref
const file = Bun.file("output.txt");
const writer = file.writer();
writer.unref();
// to "re-ref" it later
writer.ref();
Example: Read directory with readdir
import { readdir } from "node:fs/promises";
// read all the files in the current directory
const files = await readdir(import.meta.dir);
Example: Read directory recursively
import { readdir } from "node:fs/promises";
// read all the files in the current directory, recursively
const files = await readdir("../", { recursive: true });
Example: Create directory recursively
import { mkdir } from "node:fs/promises";
await mkdir("path/to/dir", { recursive: true });
Example: Linux cat command implementation
// Usage: bun ./cat.ts ./path-to-file
import { resolve } from "path";
const path = resolve(process.argv.at(-1));
await Bun.write(Bun.stdout, Bun.file(path));
TypeScript interface: Bun file i/o methods
interface Bun { stdin: BunFile; stdout: BunFile; stderr: BunFile; file(path: string | number | URL, options?: { type?: string }): BunFile; write(destination: string | number | BunFile | URL, input: string | Blob | ArrayBuffer | SharedArrayBuffer | TypedArray | Response): Promise<number>; }
TypeScript interface: BunFile
interface BunFile { readonly size: number; readonly type: string; text(): Promise<string>; stream(): ReadableStream; arrayBuffer(): Promise<ArrayBuffer>; json(): Promise<any>; bytes(): Promise<Uint8Array>; writer(params?: { highWaterMark?: number }): FileSink; exists(): Promise<boolean>; delete(): Promise<void>; }
TypeScript interface: FileSink
export interface FileSink { write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number; flush(): number | Promise<number>; end(error?: Error): number | Promise<number>; start(options?: { highWaterMark?: number }): void; ref(): void; unref(): void; }
Bun.file and Bun.write are heavily optimized
The Bun.file and Bun.write APIs are heavily optimized and recommended for file operations. For operations not covered by these APIs, such as mkdir or readdir, use Bun's nearly complete implementation of the node:fs module.
Serving files with Bun.file in routes
In Bun.serve routes, you can use Bun.file(path) to lazily load and serve a file. Example: '/favicon.ico': Bun.file('./favicon.ico'). The file is loaded into memory only when the route is accessed.
node:fs fully implemented
node:fs is fully implemented. 98% of Node.js's test suite passes. Stats objects lack the Temporal.Instant getters (atimeInstant and friends).
fetch write response to file
You can use Bun.write to write the response body directly to a file on disk.