Use Bun
Bun-first philosophy: prefer native APIs over external packages.
- Auditing existing projects for Bun migration opportunities
- Starting new TypeScript projects
- Evaluating whether to add a dependency
- Reviewing code that uses Node.js APIs
- Cleaning up package.json bloat
Boundary: This skill covers when to use Bun and what it replaces. For detailed Bun API patterns and implementation examples, load the outfitter:bun-dev skill instead.
CLI Commands
Use Bun directly instead of Node.js tooling:
| Instead of | Use |
|---|---|
node file.ts or ts-node file.ts |
bun file.ts |
jest or vitest |
bun test |
webpack or esbuild (CLI) |
bun build |
npm install / yarn / pnpm |
bun install |
npm run script |
bun run script |
npx package |
bunx package |
nodemon |
bun --watch |
node --env-file=.env |
bun (auto-loads .env) |
Decision Framework
Before adding a dependency, follow this hierarchy:
Need functionality
│
├─► Does Bun have a built-in API?
│ └─► YES → Use it directly
│
├─► Can you wrap a Bun primitive?
│ └─► YES → Thin abstraction over Bun API
│
└─► External package (last resort)
└─► Document why Bun couldn't do it
Evaluation Checklist
- Check Bun docs first: https://bun.sh/docs
- Search for
Bun.orbun:in docs - Test if Node.js API you're using has faster Bun equivalent
- If adding package, verify Bun doesn't cover it natively
When Packages Are Justified
- Framework-level abstractions (Hono, TanStack Router)
- Domain-specific logic (Zod schemas, date-fns)
- Protocol implementations Bun doesn't cover
- Battle-tested crypto beyond basic hashing
Document exceptions with a code comment:
// Using date-fns: Bun has no date manipulation APIs
import { format, addDays } from 'date-fns';
Quick Reference
Bun APIs organized by category. Check these before reaching for npm.
Testing
| Bun API | Replaces |
|---|---|
bun:test |
jest, vitest, mocha |
expect() |
chai, expect.js |
import { describe, test, expect } from 'bun:test';
Database & Storage
| Bun API | Replaces |
|---|---|
bun:sqlite |
better-sqlite3, sql.js |
Bun.sql |
pg, postgres.js, mysql2 |
Bun.redis |
ioredis, redis |
Bun.s3 |
@aws-sdk/client-s3 |
import { Database } from 'bun:sqlite';
const client = Bun.redis();
const bucket = Bun.s3('my-bucket');
Networking
| Bun API | Replaces |
|---|---|
Bun.listen() |
net.createServer |
Bun.connect() |
net.connect |
Bun.udpSocket() |
dgram.createSocket |
Bun.dns |
dns.lookup |
HTTP
| Bun API | Replaces |
|---|---|
Bun.serve() |
express, fastify, http |
Bun.fetch() |
node-fetch, axios, got |
Bun.serve({ port: 3000, fetch: (req) => new Response('ok') });
Shell & Process
| Bun API | Replaces |
|---|---|
Bun.$ |
execa, shelljs, zx |
Bun.spawn() |
child_process.spawn |
Bun.spawnSync() |
child_process.spawnSync |
import { $ } from 'bun';
await $`ls -la`;
File System
| Bun API | Replaces |
|---|---|
Bun.file() |
fs.readFile, fs-extra |
Bun.write() |
fs.writeFile |
file.exists() |
fs.existsSync |
file.stream() |
fs.createReadStream |
const content = await Bun.file('./data.json').json();
await Bun.write('./out.txt', content);
Utilities
| Bun API | Replaces |
|---|---|
Bun.password.hash() |
bcrypt, argon2 |
Bun.hash() |
xxhash, murmur, crc32 |
Bun.CryptoHasher |
crypto.createHash |
Bun.Glob |
glob, fast-glob, minimatch |
Bun.semver |
semver |
Bun.YAML.parse() |
js-yaml, yaml |
Bun.TOML.parse() |
toml |
Bun.gzipSync() |
zlib, pako |
Bun.zstdCompressSync() |
zstd-codec |
Bun.Archive |
tar, archiver |
Bun.sleep() |
delay, timers-promises |
Bun.deepEquals() |
lodash.isEqual, deep-equal |
Bun.escapeHTML() |
escape-html |
Bun.stringWidth() |
string-width |
Bun.Cookie |
cookie, tough-cookie |
Bun.randomUUIDv7() |
uuid |
Bun.which() |
which |
const hash = await Bun.password.hash(password, { algorithm: 'argon2id' });
const glob = new Bun.Glob('**/*.ts');
const valid = Bun.semver.satisfies('1.2.3', '>=1.0.0');
Bundling
| Bun API | Replaces |
|---|---|
Bun.build() |
esbuild, rollup, webpack |
bun build --compile |
pkg, nexe |
bun build ./index.ts --outfile dist/bundle.js --minify
Frontend Development
Bun.serve() supports HTML imports with automatic bundling. No Vite/Webpack needed.
// server.ts
import index from "./index.html";
Bun.serve({
routes: {
"/": index,
"/api/data": (req) => Response.json({ ok: true }),
},
development: { hmr: true, console: true },
});
HTML files can import .tsx/.jsx/.ts directly:
<!-- index.html -->
<html>
<body>
<div id="root"></div>
<script type="module" src="./app.tsx"></script>
</body>
</html>
Run with hot reloading:
bun --hot server.ts
Audit Command
Scan a codebase for Bun migration opportunities using the bundled audit script.
# From the skill directory (or adjust path as needed)
bun ./scripts/audit-bun-usage.ts [path]
# JSON output (default) - pipe to jq, use in CI
bun ./scripts/audit-bun-usage.ts ./my-project
# Markdown output - human readable
bun ./scripts/audit-bun-usage.ts ./my-project --format=md
The audit identifies:
- npm packages replaceable by Bun built-ins
- Node.js imports with Bun equivalents
- Config files for tools Bun replaces
- Files already using Bun APIs (positive signal)
Common Migrations
From Express/Fastify
// Before: Express
import express from 'express';
const app = express();
app.get('/', (req, res) => res.send('ok'));
app.listen(3000);
// After: Bun.serve + Hono
import { Hono } from 'hono';
const app = new Hono().get('/', (c) => c.text('ok'));
Bun.serve({ port: 3000, fetch: app.fetch });
From Jest/Vitest
// Before: Jest
import { describe, it, expect } from '@jest/globals';
// After: bun:test (drop-in compatible)
import { describe, it, expect } from 'bun:test';
From better-sqlite3
// Before
import Database from 'better-sqlite3';
// After (same API)
import { Database } from 'bun:sqlite';
From bcrypt
// Before
import bcrypt from 'bcrypt';
const hash = await bcrypt.hash(password, 10);
// After
const hash = await Bun.password.hash(password, { algorithm: 'argon2id' });
From execa/shelljs
// Before
import { execa } from 'execa';
const { stdout } = await execa('ls', ['-la']);
// After
import { $ } from 'bun';
const result = await $`ls -la`;
console.log(result.text());
ALWAYS:
- Check Bun docs before adding any dependency
- Use Bun.file/Bun.write over fs module
- Use bun:test for testing (Jest-compatible)
- Use bun:sqlite for SQLite (better-sqlite3 compatible)
- Use Bun.password for password hashing
- Use
Bun.$for shell commands
NEVER:
- Add packages that duplicate Bun built-ins without documenting why
- Use node-fetch when Bun.fetch exists
- Use bcrypt when Bun.password exists
- Use fs when Bun.file/Bun.write exists
- Use child_process when Bun.$/Bun.spawn exists
DOCUMENT:
- Why a package is needed when Bun alternative exists
- Bun limitations encountered (helps track what to migrate later)
- Exceptions in code comments for future audits
Related skills:
outfitter:bun-dev— Detailed Bun API patterns, implementation examples, testing strategiesoutfitter:typescript-dev— TypeScript patterns for Bun projects, strict typing, Zod validation