Bun Development
Bun runtime → native APIs → zero-dependency patterns.
<when_to_use>
-
Bun runtime development
-
SQLite database with bun:sqlite
-
HTTP server with Bun.serve
-
Testing with bun:test
-
File operations with Bun.file/Bun.write
-
Shell operations with $ template
-
Password hashing with Bun.password
-
Environment variable handling
-
Building and bundling
NOT for: Node.js-only patterns, cross-runtime libraries, non-Bun projects
</when_to_use>
<runtime_basics>
Package management:
bun install # Install deps bun add zod # Add package bun remove zod # Remove package bun update # Update all
Script execution:
bun run dev # Run package.json script bun run src/index.ts # Execute TypeScript directly bun --watch index.ts # Watch mode
Testing:
bun test # All tests bun test src/ # Directory bun test --watch # Watch mode bun test --coverage # With coverage
Building:
bun build ./index.ts --outfile dist/bundle.js bun build ./index.ts --compile --outfile myapp # Standalone executable
</runtime_basics>
File Operations
<file_operations>
// Read file (lazy, efficient) const file = Bun.file('./data.json'); if (!(await file.exists())) throw new Error('File not found');
// Read formats const text = await file.text(); const json = await file.json(); const buffer = await file.arrayBuffer(); const stream = file.stream(); // Large files
// Metadata console.log(file.size, file.type);
// Write await Bun.write('./output.txt', 'content'); await Bun.write('./data.json', JSON.stringify(data)); await Bun.write('./blob.txt', new Blob(['data']));
</file_operations>
SQLite (bun:sqlite)
import { Database } from 'bun:sqlite';
const db = new Database('app.db', { create: true, readwrite: true, strict: true });
// Create tables
db.run( CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT UNIQUE NOT NULL, name TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP ));
// Prepared statements (always use these) const getUser = db.prepare('SELECT * FROM users WHERE id = ?'); const createUser = db.prepare('INSERT INTO users (id, email, name) VALUES (?, ?, ?) RETURNING *');
// Execution const user = getUser.get('user-123'); // Single row const all = db.prepare('SELECT * FROM users').all(); // All rows db.prepare('DELETE FROM users WHERE id = ?').run('id'); // No return
// Named parameters const stmt = db.prepare('SELECT * FROM users WHERE email = $email'); stmt.get({ $email: 'alice@example.com' });
// Transactions (atomic, auto-rollback on error) const transfer = db.transaction((fromId: string, toId: string, amount: number) => { db.run('UPDATE accounts SET balance = balance - ? WHERE id = ?', [amount, fromId]); db.run('UPDATE accounts SET balance = balance + ? WHERE id = ?', [amount, toId]); }); transfer('alice', 'bob', 100);
db.close(); // When done
See sqlite-patterns.md for migrations, pooling, repository pattern.
Password Hashing
// Hash (argon2id recommended) const hash = await Bun.password.hash('password123', { algorithm: 'argon2id', memoryCost: 65536, // 64 MB timeCost: 3 });
// Or bcrypt const bcryptHash = await Bun.password.hash('password123', { algorithm: 'bcrypt', cost: 12 });
// Verify const isValid = await Bun.password.verify('password123', hash); if (!isValid) throw new Error('Invalid password');
Auth flow example:
app.post('/auth/register', zValidator('json', RegisterSchema), async (c) => { const { email, password } = c.req.valid('json'); const db = c.get('db');
if (db.prepare('SELECT id FROM users WHERE email = ?').get(email)) { throw new HTTPException(409, { message: 'Email already registered' }); }
const hashedPassword = await Bun.password.hash(password, { algorithm: 'argon2id' });
const user = db.prepare( INSERT INTO users (id, email, password) VALUES (?, ?, ?) RETURNING id, email ).get(crypto.randomUUID(), email, hashedPassword);
return c.json({ user }, 201); });
HTTP Server
<http_server>
Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === '/') return new Response('Hello');
if (url.pathname === '/json') return Response.json({ ok: true });
return new Response('Not found', { status: 404 });
},
error(err) {
return new Response(Error: ${err.message}, { status: 500 });
}
});
With Hono (recommended for APIs):
import { Hono } from 'hono';
const app = new Hono() .get('/', (c) => c.text('Hello')) .get('/json', (c) => c.json({ ok: true }));
Bun.serve({ port: 3000, fetch: app.fetch });
See server-patterns.md for routing, middleware, file serving, streaming.
</http_server>
WebSocket
import type { ServerWebSocket } from 'bun';
type WsData = { userId: string };
Bun.serve<WsData>({ port: 3000, fetch(req, server) { const url = new URL(req.url); if (url.pathname === '/ws') { const userId = url.searchParams.get('userId') || 'anon'; return server.upgrade(req, { data: { userId } }) ? undefined : new Response('Upgrade failed', { status: 400 }); } return new Response('Hello'); }, websocket: { open(ws: ServerWebSocket<WsData>) { ws.subscribe('chat'); ws.send(JSON.stringify({ type: 'connected' })); }, message(ws: ServerWebSocket<WsData>, msg: string | Buffer) { ws.publish('chat', msg); }, close(ws: ServerWebSocket<WsData>) { ws.unsubscribe('chat'); } } });
See server-patterns.md for client tracking, rooms, reconnection.
Shell Operations
import { $ } from 'bun';
// Run commands
const result = await $ls -la;
console.log(result.text());
// Variables (auto-escaped)
const dir = './src';
await $find ${dir} -name "*.ts";
// Check exit code
const { exitCode } = await $npm test.nothrow();
if (exitCode !== 0) console.error('Tests failed');
// Spawn process const proc = Bun.spawn(['ls', '-la']); await proc.exited;
// Capture output const proc2 = Bun.spawn(['echo', 'Hello'], { stdout: 'pipe' }); const output = await new Response(proc2.stdout).text();
Testing (bun:test)
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
describe('feature', () => { let db: Database;
beforeEach(() => { db = new Database(':memory:'); }); afterEach(() => { db.close(); });
test('behavior', () => { expect(result).toBe(expected); expect(arr).toContain(item); expect(fn).toThrow(); expect(obj).toEqual({ foo: 'bar' }); });
test('async', async () => { const result = await asyncFn(); expect(result).toBeDefined(); });
test.todo('pending feature'); test.skip('temporarily disabled'); });
bun test # All tests bun test src/api.test.ts # Specific file bun test --watch # Watch mode bun test --coverage # With coverage
See testing.md for assertions, mocking, snapshots, best practices.
Environment Variables
// Access console.log(Bun.env.NODE_ENV); console.log(Bun.env.DATABASE_URL);
// Zod validation import { z } from 'zod';
const EnvSchema = z.object({ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), DATABASE_URL: z.string(), PORT: z.coerce.number().int().positive().default(3000), API_KEY: z.string().min(32) });
export const env = EnvSchema.parse(Bun.env);
Bun auto-loads .env , .env.local , .env.production .
Performance Utilities
// High-resolution timing
const start = Bun.nanoseconds();
await doWork();
console.log(Took ${(Bun.nanoseconds() - start) / 1_000_000}ms);
// Hashing const hash = Bun.hash(data); const crc32 = Bun.hash.crc32(data); const sha256 = Bun.CryptoHasher.hash('sha256', data);
// Sleep await Bun.sleep(1000);
// Memory const { rss, heapUsed } = process.memoryUsage(); console.log('RSS:', rss / 1024 / 1024, 'MB');
Building & Bundling
Production bundle
bun build ./index.ts --outfile dist/bundle.js --minify --sourcemap
External deps
bun build ./index.ts --outfile dist/bundle.js --external hono --external zod
Standalone executable
bun build ./index.ts --compile --outfile myapp
Cross-compile
bun build ./index.ts --compile --target=bun-linux-x64 --outfile myapp-linux bun build ./index.ts --compile --target=bun-darwin-arm64 --outfile myapp-macos bun build ./index.ts --compile --target=bun-windows-x64 --outfile myapp.exe
ALWAYS:
-
Use Bun APIs when available (faster, native)
-
Prepared statements for database queries
-
Transactions for multi-statement operations
-
argon2id for password hashing
-
Validate environment variables at startup
-
Close database connections when done
NEVER:
-
String interpolation in SQL (use parameters)
-
Plaintext passwords
-
Ignore async disposal cleanup
-
Deprecated Node.js APIs when Bun native exists
PREFER:
-
Bun.file over fs.readFile
-
Bun.write over fs.writeFile
-
bun:sqlite over external SQLite libraries
-
Bun.password over bcrypt/argon2 packages
-
$ shell template over child_process
-
sqlite-patterns.md — migrations, pooling, repository, FTS
-
server-patterns.md — HTTP, WebSocket, streaming, compression
-
testing.md — assertions, mocking, snapshots, best practices
Examples:
-
database-crud.md — SQLite CRUD patterns
-
file-uploads.md — streaming file handling