bun-dev
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
More from outfitter-dev/agents
graphite-stacks
This skill should be used when the user asks to "create a stack", "submit stacked PRs", "gt submit", "gt create", "reorganize branches", "fix stack corruption", or mentions Graphite, stacked PRs, gt commands, or trunk-based development workflows.
76code-review
This skill should be used when reviewing code before commit, conducting quality gates, or when "review", "fresh eyes", "pre-commit review", or "quality gate" are mentioned.
34hono-dev
This skill should be used when building APIs with Hono, using hc client, implementing OpenAPI, or when "Hono", "RPC", or "type-safe API" are mentioned.
28software-craft
This skill should be used when making design decisions, evaluating trade-offs, assessing code quality, or when "engineering judgment" or "code quality" are mentioned.
28subagents
This skill should be used when coordinating agents, delegating tasks to specialists, or when "dispatch agents", "which agent", or "multi-agent" are mentioned.
25context-management
Manage context window, survive compaction, persist state. Use when planning long tasks, coordinating agents, approaching context limits, or when "context", "compaction", "tasks", or "persist state" are mentioned.
24