Bun Runtime APIs
Bun-native runtime APIs including Bun.serve(), Bun.file(), Bun.write(), Bun.spawn(), Bun.sleep(), Bun.env, FFI for calling native libraries, built-in SQLite, S3 client, glob, and semver utilities.
You are an expert in Bun's native runtime APIs, covering file I/O, process spawning, environment access, foreign function interface, built-in SQLite, and utility modules.
## Key Points
- **Using `fs.readFile` when `Bun.file()` is available**: The Bun-native API is faster and more ergonomic. Use `Bun.file()` for reads and `Bun.write()` for writes.
- **String-interpolating SQL queries**: Always use prepared statements with `db.prepare()`. SQLite injection is just as dangerous as SQL injection in any other database.
- **Loading SQLite from npm when `bun:sqlite` exists**: The built-in module is faster and has zero dependencies. Only use `better-sqlite3` if you need a feature Bun's SQLite does not expose.
- **Using child_process.exec for shell commands**: Prefer `Bun.spawn()` or `Bun.$` for shell scripting. They are faster and have better ergonomics.
- **Forgetting to null-terminate FFI strings**: C functions expect null-terminated strings. Always append `\0` when passing strings via FFI.
- **Blocking the event loop with Bun.sleepSync**: Use `await Bun.sleep()` for async delays. The sync version blocks all other work.
## Quick Example
```typescript
// Sleep for milliseconds
await Bun.sleep(1000); // 1 second
// Synchronous sleep (blocks the thread)
Bun.sleepSync(100);
```
```typescript
import { semver } from "bun";
console.log(semver.satisfies("1.2.3", "^1.0.0")); // true
console.log(semver.satisfies("2.0.0", "^1.0.0")); // false
console.log(semver.order("1.2.3", "1.3.0")); // -1 (first is less)
```skilldb get bun-skills/Bun Runtime APIsFull skill: 344 linesBun Runtime APIs — Native High-Performance APIs
You are an expert in Bun's native runtime APIs, covering file I/O, process spawning, environment access, foreign function interface, built-in SQLite, and utility modules.
Bun.file() and Bun.write() — Optimized File I/O
Bun provides its own file APIs that are faster than Node's fs module because they use optimized system calls (io_uring on Linux).
// Reading files
const file = Bun.file("data.json");
console.log(file.size); // file size in bytes
console.log(file.type); // MIME type: "application/json"
const text = await file.text(); // read as string
const json = await file.json(); // parse as JSON
const buffer = await file.arrayBuffer(); // read as ArrayBuffer
const stream = file.stream(); // ReadableStream
// Check if a file exists
const exists = await Bun.file("maybe.txt").exists();
// Writing files
await Bun.write("output.txt", "Hello, Bun!");
await Bun.write("data.json", JSON.stringify({ key: "value" }));
await Bun.write("copy.txt", Bun.file("original.txt")); // copy
await Bun.write("image.png", await fetch("https://example.com/img.png"));
// Write to stdout
await Bun.write(Bun.stdout, "Hello to stdout\n");
// Write with Response object
await Bun.write("page.html", new Response("<h1>Hi</h1>"));
Bun.file() is lazy -- it does not read the file until you call .text(), .json(), .arrayBuffer(), or .stream(). This means Bun.file("big.csv") is instant and allocates no memory until you consume it.
Bun.serve() — HTTP and WebSocket Server
const server = Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/api/health") {
return Response.json({ status: "ok" });
}
return new Response("Not Found", { status: 404 });
},
error(err) {
return new Response(`Error: ${err.message}`, { status: 500 });
},
});
console.log(`Listening on ${server.url}`);
See the bun-http-server skill for detailed HTTP server patterns.
Bun.spawn() — Process Spawning
// Simple command
const proc = Bun.spawn(["ls", "-la"]);
const output = await new Response(proc.stdout).text();
console.log(output);
// With options
const proc2 = Bun.spawn(["grep", "error"], {
cwd: "/var/log",
env: { ...Bun.env, CUSTOM: "value" },
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
// Write to stdin
proc2.stdin.write("line with error\nclean line\n");
proc2.stdin.end();
const result = await new Response(proc2.stdout).text();
await proc2.exited; // wait for process to finish
console.log(proc2.exitCode); // 0
// Synchronous spawn
const syncResult = Bun.spawnSync(["echo", "hello"]);
console.log(syncResult.stdout.toString()); // "hello\n"
console.log(syncResult.exitCode); // 0
Shell scripting with Bun.$
Bun provides a tagged template literal for shell commands:
import { $ } from "bun";
// Simple commands
const result = await $`ls -la`.text();
// Interpolation is safe (auto-escaped)
const dir = "my folder";
await $`ls ${dir}`; // properly handles spaces
// Piping
const count = await $`cat access.log | grep 404 | wc -l`.text();
// Quiet mode (suppress stdout)
await $`rm -rf dist`.quiet();
// Get exit code without throwing on failure
const { exitCode } = await $`test -f config.json`.nothrow();
// Environment variables
await $`echo $HOME`;
// Redirect to file
await $`echo "hello" > output.txt`;
Bun.sleep() — Async Delay
// Sleep for milliseconds
await Bun.sleep(1000); // 1 second
// Synchronous sleep (blocks the thread)
Bun.sleepSync(100);
Bun.env — Environment Variables
// Read environment variables (same as process.env but typed)
const port = Bun.env.PORT ?? "3000";
const dbUrl = Bun.env.DATABASE_URL;
// Bun automatically loads .env files in this order:
// 1. .env.local
// 2. .env.development / .env.production (based on NODE_ENV)
// 3. .env
// You can also specify env files explicitly:
// bun --env-file=.env.custom run server.ts
// Type-safe env access
if (!Bun.env.API_KEY) {
throw new Error("API_KEY is required");
}
FFI — Foreign Function Interface
Call native C/C++/Rust shared libraries directly from JavaScript without writing native addons:
import { dlopen, FFIType, suffix } from "bun:ffi";
// Load a shared library (.so on Linux, .dylib on macOS, .dll on Windows)
const lib = dlopen(`libcalc.${suffix}`, {
add: {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
},
multiply: {
args: [FFIType.f64, FFIType.f64],
returns: FFIType.f64,
},
});
console.log(lib.symbols.add(2, 3)); // 5
console.log(lib.symbols.multiply(2.5, 4)); // 10.0
// Close the library when done
lib.close();
Supported FFI types: bool, i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, ptr, cstring, void.
// Working with pointers and strings
const lib = dlopen("libgreet.so", {
greet: {
args: [FFIType.cstring],
returns: FFIType.cstring,
},
});
const encoder = new TextEncoder();
const name = encoder.encode("World\0"); // null-terminated
const result = lib.symbols.greet(name);
Built-in SQLite
Bun embeds SQLite directly -- no npm packages needed:
import { Database } from "bun:sqlite";
// Open or create a database
const db = new Database("app.db");
// Create tables
db.run(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`);
// Prepared statements (always use these -- never interpolate SQL)
const insert = db.prepare(
"INSERT INTO users (name, email) VALUES ($name, $email)"
);
insert.run({ $name: "Alice", $email: "alice@example.com" });
// Query rows
const select = db.prepare("SELECT * FROM users WHERE name = ?");
const user = select.get("Alice"); // single row
const allUsers = db.prepare("SELECT * FROM users").all(); // all rows
// Transactions
const insertMany = db.transaction((users: { name: string; email: string }[]) => {
for (const u of users) {
insert.run({ $name: u.name, $email: u.email });
}
});
insertMany([
{ name: "Bob", email: "bob@example.com" },
{ name: "Carol", email: "carol@example.com" },
]);
// WAL mode for better concurrent read performance
db.exec("PRAGMA journal_mode = WAL");
// In-memory database
const memDb = new Database(":memory:");
// Close when done
db.close();
S3 Client
Bun includes a built-in S3 client that works with any S3-compatible storage:
import { S3Client } from "bun";
const s3 = new S3Client({
accessKeyId: Bun.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: Bun.env.AWS_SECRET_ACCESS_KEY!,
region: "us-east-1",
// endpoint: "https://s3.example.com", // for S3-compatible services
});
// Write a file
await s3.write("my-bucket/hello.txt", "Hello from Bun!");
// Read a file
const file = s3.file("my-bucket/data.json");
const data = await file.json();
// Check existence
const exists = await s3.file("my-bucket/maybe.txt").exists();
// Delete
await s3.delete("my-bucket/old-file.txt");
// The S3 file object has the same interface as Bun.file()
const size = s3.file("my-bucket/large.csv").size;
const stream = s3.file("my-bucket/large.csv").stream();
Glob
import { Glob } from "bun";
const glob = new Glob("**/*.ts");
// Scan files matching the pattern
for await (const path of glob.scan({ cwd: "./src" })) {
console.log(path); // "index.ts", "utils/helpers.ts", etc.
}
// Synchronous scan
const files = Array.from(glob.scanSync({ cwd: "./src" }));
// Test if a string matches a glob pattern
const g = new Glob("*.json");
console.log(g.match("package.json")); // true
console.log(g.match("src/data.json")); // false (no ** prefix)
Semver
import { semver } from "bun";
console.log(semver.satisfies("1.2.3", "^1.0.0")); // true
console.log(semver.satisfies("2.0.0", "^1.0.0")); // false
console.log(semver.order("1.2.3", "1.3.0")); // -1 (first is less)
Hashing
// Fast hashing built into Bun
const hash = Bun.hash("hello world"); // uint64, very fast (Wyhash)
// Crypto hashing
const sha256 = Bun.CryptoHasher.hash("sha256", "hello");
console.log(Buffer.from(sha256).toString("hex"));
// Streaming hash
const hasher = new Bun.CryptoHasher("sha256");
hasher.update("hello ");
hasher.update("world");
const digest = hasher.digest("hex");
// Password hashing (bcrypt and argon2)
const hashed = await Bun.password.hash("mypassword");
const valid = await Bun.password.verify("mypassword", hashed); // true
Anti-Patterns
- Using
fs.readFilewhenBun.file()is available: The Bun-native API is faster and more ergonomic. UseBun.file()for reads andBun.write()for writes. - String-interpolating SQL queries: Always use prepared statements with
db.prepare(). SQLite injection is just as dangerous as SQL injection in any other database. - Loading SQLite from npm when
bun:sqliteexists: The built-in module is faster and has zero dependencies. Only usebetter-sqlite3if you need a feature Bun's SQLite does not expose. - Using child_process.exec for shell commands: Prefer
Bun.spawn()orBun.$for shell scripting. They are faster and have better ergonomics. - Forgetting to null-terminate FFI strings: C functions expect null-terminated strings. Always append
\0when passing strings via FFI. - Blocking the event loop with Bun.sleepSync: Use
await Bun.sleep()for async delays. The sync version blocks all other work.
Install this skill directly: skilldb add bun-skills
Related Skills
Bun Bundler
Bun's built-in bundler: Bun.build() API, entry points, output formats (esm, cjs, iife), plugins, loaders, tree shaking, code splitting, CSS bundling, HTML entries, and compile-time macros.
Bun Fundamentals
Bun runtime overview: all-in-one JavaScript runtime, bundler, test runner, and package manager. Installation, project initialization, Node.js compatibility, performance characteristics, and guidance on when to choose Bun vs Node.
Bun HTTP Server
Building HTTP servers with Bun: Bun.serve() API, routing patterns, WebSocket support, streaming responses, static file serving, TLS configuration, hot reloading, and integration with frameworks like Hono and Elysia.
Bun Node.js Migration
Migrating from Node.js to Bun: compatibility checklist, node:* module imports, native addon handling, environment variable differences, Docker setup, CI/CD pipeline changes, and common migration pitfalls.
Bun Package Manager
Bun as a package manager: bun install, bun add, bun remove, the binary lockfile (bun.lockb), workspace support, overrides, patching, publishing packages, global cache, and comparison to npm, pnpm, and yarn.
Bun Production Patterns
Production patterns for Bun: Docker deployments, TypeScript configuration, shell scripting with Bun.$, monorepo setup, database access patterns, and deployment to Fly.io and Railway.