Claude
Skills
Sign in
Back

bun-runtime-best-practices

Included with Lifetime
$97 forever

Bun runtime best practices and API usage patterns When user works with file I/O, environment variables, subprocess spawning, or uses Node.js APIs that have Bun equivalents

Backend & APIs

What this skill does


# Bun Runtime Best Practices Agent

## What's New in Bun 1.3 (October 2025)

- **Unified SQL API**: Built-in PostgreSQL, MySQL/MariaDB, and SQLite clients (`Bun.SQL`)
- **Zero Dependencies**: One incredibly fast database library, no external packages needed
- **Full-Stack Dev Server**: Zero-config frontend development with hot reloading
- **Built-in Redis Client**: Native Redis support without additional packages
- **Standalone Executables**: Cross-platform compilation for distribution
- **8x Faster Startup**: Compared to Node.js, with 145k req/s HTTP throughput (Node: 65k)
- **Isolated Installs**: Minimize dependency conflicts across projects
- **Vercel Runtime Support**: Deploy Bun apps to Vercel seamlessly

## Overview

This agent teaches Bun-specific APIs and patterns to replace Node.js equivalents, based on coding standards from scout-for-lol and homelab repositories. Bun provides faster, more modern alternatives to Node.js APIs.

**Performance Note**: Bun 1.3 delivers 8x faster startup than Node.js with 145k requests/second HTTP server performance. Built-in database clients (PostgreSQL, MySQL, SQLite, Redis) eliminate external dependencies while maintaining zero-config simplicity.

## Core Principle

**Prefer Bun APIs over Node.js imports** for better performance and modern patterns.

## File I/O

### Use Bun.file() and Bun.write()

**❌ Avoid: Node.js fs module**

```typescript
import fs from "fs";
import { promises as fs } from "fs/promises";
import * as fs from "node:fs/promises";

// Don't use these
const content = await fs.readFile("file.txt", "utf-8");
await fs.writeFile("file.txt", "content");
```

**✅ Prefer: Bun.file() and Bun.write()**

```typescript
// Reading files
const file = Bun.file("file.txt");
const content = await file.text();
const json = await file.json();
const arrayBuffer = await file.arrayBuffer();
const stream = file.stream();

// Writing files
await Bun.write("output.txt", "Hello, world!");
await Bun.write("data.json", JSON.stringify({ foo: "bar" }));
await Bun.write("binary.dat", new Uint8Array([1, 2, 3]));

// With options
await Bun.write("file.txt", "content", {
  createPath: true, // Create parent directories
});
```

### File Operations

```typescript
// Check if file exists
const file = Bun.file("file.txt");
const exists = await file.exists();

// Get file size
const size = file.size;

// Get file type
const type = file.type; // MIME type

// Read file in chunks
const file = Bun.file("large-file.txt");
for await (const chunk of file.stream()) {
  console.log(chunk);
}
```

## Environment Variables

### Use Bun.env Instead of process.env

**❌ Avoid: process.env**

```typescript
const apiKey = process.env.API_KEY;
const port = process.env.PORT || "3000";
```

**✅ Prefer: Bun.env**

```typescript
const apiKey = Bun.env.API_KEY;
const port = Bun.env.PORT ?? "3000";

// Bun.env is typed and provides better autocomplete
// Load from .env file automatically
```

### Environment Variable Validation

```typescript
import { z } from "zod";

// Validate environment variables with Zod
const EnvSchema = z.object({
  DATABASE_URL: z.string().url(),
  API_KEY: z.string().min(1),
  PORT: z.coerce.number().int().positive().default(3000),
  NODE_ENV: z.enum(["development", "production", "test"]),
});

const env = EnvSchema.parse(Bun.env);
```

## Process Spawning

### Use Bun.spawn() Instead of child_process

**❌ Avoid: child_process**

```typescript
import { spawn } from "child_process";
import { exec } from "node:child_process";

const child = spawn("ls", ["-la"]);
```

**✅ Prefer: Bun.spawn()**

```typescript
// Simple command
const proc = Bun.spawn(["ls", "-la"]);
const output = await new Response(proc.stdout).text();
console.log(output);

// With options
const proc = Bun.spawn(["git", "status"], {
  cwd: "/path/to/repo",
  env: { ...Bun.env, GIT_AUTHOR_NAME: "Bot" },
  stdout: "pipe",
  stderr: "pipe",
});

// Read output
const stdout = await new Response(proc.stdout).text();
const stderr = await new Response(proc.stderr).text();

// Wait for exit
const exitCode = await proc.exited;
```

### Running Shell Commands

```typescript
// Execute shell command
const proc = Bun.spawn(["sh", "-c", "echo Hello && date"], {
  stdout: "pipe",
});

const output = await new Response(proc.stdout).text();

// Pipe to another command
const proc1 = Bun.spawn(["ls", "-la"], { stdout: "pipe" });
const proc2 = Bun.spawn(["grep", ".ts"], {
  stdin: proc1.stdout,
  stdout: "pipe",
});

const result = await new Response(proc2.stdout).text();
```

## Path Handling

### Use import.meta.dir and import.meta.path

**❌ Avoid: path module and \_\_dirname**

```typescript
import path from "path";
import { dirname } from "node:path";

const dir = __dirname;
const file = __filename;
const joined = path.join(__dirname, "config.json");
```

**✅ Prefer: import.meta**

```typescript
// Get current directory
const currentDir = import.meta.dir;

// Get current file path
const currentFile = import.meta.path;

// Join paths
const configPath = `${import.meta.dir}/config.json`;

// Or use Bun.file() with relative paths
const config = Bun.file("./config.json"); // Relative to current file
```

### Path Utilities

```typescript
// Resolve absolute path
import { resolve } from "path"; // Can still use for complex operations

const absolutePath = resolve(import.meta.dir, "../config.json");

// But prefer simpler string operations when possible
const configPath = `${import.meta.dir}/../config.json`;
```

## Cryptography

### Use Bun.password, Bun.hash(), or Web Crypto API

**❌ Avoid: crypto module**

```typescript
import crypto from "crypto";
import { createHash } from "node:crypto";

const hash = crypto.createHash("sha256").update("data").digest("hex");
```

**✅ Prefer: Bun APIs**

```typescript
// Password hashing
const hashedPassword = await Bun.password.hash("my-password");
const isValid = await Bun.password.verify("my-password", hashedPassword);

// With options
const hashedPassword = await Bun.password.hash("my-password", {
  algorithm: "argon2id", // or "bcrypt", "scrypt"
  memoryCost: 65536,
  timeCost: 3,
});

// Hashing (SHA, MD5, etc.)
const hasher = new Bun.CryptoHasher("sha256");
hasher.update("data");
const hash = hasher.digest("hex");

// One-liner
const hash = Bun.hash("data"); // Returns integer hash

// Web Crypto API for advanced crypto
const encoder = new TextEncoder();
const data = encoder.encode("data");
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
```

## Binary Data

### Prefer Uint8Array Over Buffer

**❌ Avoid: Buffer**

```typescript
const buffer = Buffer.from("hello");
const buffer2 = Buffer.alloc(10);
```

**✅ Prefer: Uint8Array and Bun APIs**

```typescript
// Create binary data
const encoder = new TextEncoder();
const bytes = encoder.encode("hello");

// Bun.file() handles binary data natively
const file = Bun.file("image.png");
const arrayBuffer = await file.arrayBuffer();
const bytes = new Uint8Array(arrayBuffer);

// Write binary data
await Bun.write("output.bin", bytes);
```

## Module System

### Use ESM Imports, Never require()

**❌ Avoid: CommonJS require**

```typescript
const fs = require("fs");
const { parse } = require("./parser");
```

**✅ Prefer: ESM imports**

```typescript
import fs from "fs";
import { parse } from "./parser.ts";

// Dynamic imports
const module = await import("./dynamic-module.ts");
```

### Import Extensions

**Always use `.ts` extensions in imports:**

```typescript
// ✅ Good
import { helper } from "./utils/helper.ts";
import type { User } from "./types/user.ts";

// ❌ Bad
import { helper } from "./utils/helper";
import type { User } from "./types/user";
```

## Bun-Specific Features

### Bun.sleep()

```typescript
// Sleep for specified time
await Bun.sleep(1000); // 1 second
await Bun.sleep(100); // 100ms

// Better than setTimeout for awaiting
```

##

Related in Backend & APIs