Bun is a JavaScript runtime, package manager, bundler, and test runner — all in one binary. Built on JavaScriptCore, written in Zig, designed to be fast at everything Node.js is slow at: startup, npm install, test execution, bundling.
Installation
# macOS / Linux
curl -fsSL https://bun.sh/install | bash
# Windows
powershell -c "irm bun.sh/install.ps1 | iex"
bun --version # 1.x.x
Package Manager
Drop-in replacement for npm, 10-30x faster on cold installs:
bun install # install all deps
bun add express
bun add -d @types/node
bun remove lodash
bun run dev # runs "dev" script
Use Bun as package manager even on Node.js projects. You don't have to switch runtimes to get faster installs.
TypeScript Natively
No ts-node, no tsx, no build step:
bun script.ts # runs TypeScript directly
bun --watch src/index.ts
bun --hot src/server.ts # hot reload
Bun strips types at runtime — it does not type-check. Run tsc --noEmit separately.
HTTP Server
const server = Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url)
if (url.pathname === '/api/posts' && req.method === 'GET') {
const posts = await db.post.findMany({ where: { published: true } })
return Response.json(posts)
}
if (url.pathname === '/api/posts' && req.method === 'POST') {
const body = await req.json()
const post = await db.post.create({ data: body })
return Response.json(post, { status: 201 })
}
return new Response('Not Found', { status: 404 })
}
})
For real APIs use Hono — it's built for Bun and adds routing, middleware, and validation:
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
app.get('/api/posts', async (c) => {
const posts = await db.post.findMany({ where: { published: true } })
return c.json(posts)
})
app.post('/api/posts',
zValidator('json', z.object({ title: z.string(), content: z.string() })),
async (c) => {
const data = c.req.valid('json')
const post = await db.post.create({ data })
return c.json(post, 201)
}
)
export default { port: 3000, fetch: app.fetch }
File APIs
// Read
const text = await Bun.file('./data.json').text()
const json = await Bun.file('./data.json').json<MyType>()
// Write
await Bun.write('./output.txt', 'Hello, World!')
await Bun.write('./data.json', JSON.stringify(data, null, 2))
// Check existence
const exists = await Bun.file('./config.json').exists()
Built-in SQLite
No npm install needed:
import { Database } from 'bun:sqlite'
const db = new Database('myapp.db')
db.run(`
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
content TEXT NOT NULL,
published INTEGER DEFAULT 0
)
`)
// Always use prepared statements
const insertPost = db.prepare(
'INSERT INTO posts (title, slug, content) VALUES (?, ?, ?)'
)
const getPost = db.prepare('SELECT * FROM posts WHERE slug = ?')
insertPost.run('Hello', 'hello', 'Content')
const post = getPost.get('hello')
Or use Drizzle ORM with the Bun adapter:
import { drizzle } from 'drizzle-orm/bun-sqlite'
import { Database } from 'bun:sqlite'
const db = drizzle(new Database('myapp.db'))
const posts = await db.select().from(postsTable)
Test Runner
Jest-compatible, near-instant startup:
bun test
bun test --watch
bun test --coverage
import { describe, it, expect, beforeEach, mock } from 'bun:test'
describe('Post operations', () => {
it('creates a post with correct slug', async () => {
const post = await createPost({ title: 'Hello World', content: '...' })
expect(post.slug).toBe('hello-world')
})
it('returns null for non-existent slug', async () => {
const post = await getPostBySlug('does-not-exist')
expect(post).toBeNull()
})
})
Most Jest tests run without modification.
Bun Shell
Cross-platform scripting in TypeScript:
import { $ } from 'bun'
// Run commands
await $`git add -A && git commit -m "commit"`
// Capture output
const result = await $`git log --oneline -5`.text()
const lines = await $`git diff --name-only main`.lines()
// Check exit code
const { exitCode } = await $`npm test`.nothrow()
if (exitCode !== 0) process.exit(1)
Works on Windows, macOS, and Linux with the same syntax.
Environment Variables
// .env loaded automatically — no dotenv package needed
const apiKey = process.env.API_KEY
const dbUrl = Bun.env.DATABASE_URL
// Type-safe validation still recommended
import { z } from 'zod'
const env = z.object({
DATABASE_URL: z.string().url(),
PORT: z.coerce.number().default(3000)
}).parse(process.env)
Bundler
bun build src/index.ts --outdir dist
bun build src/app.tsx --outdir dist --target browser --minify
When to Use Bun in Production
Good fit:
- API servers (especially with Hono/Elysia)
- CLI tools and scripts
- Background workers
- Serverless functions (faster cold starts)
Be cautious if:
- Your app uses native Node.js addons (.node files)
- You rely on specific Node.js internal APIs
Check bun.sh/nodejs-compat for current compatibility status.
Bun vs Node.js vs Deno
| Bun | Node.js | Deno | |
|---|---|---|---|
| TypeScript | Native | Needs tsx | Native |
| Test runner | Built-in | Jest/Vitest | Built-in |
| Bundler | Built-in | webpack/esbuild | Built-in |
| SQLite | Built-in | better-sqlite3 | Built-in |
| Package manager | Built-in (fast) | npm/pnpm | JSR/npm |
| Production maturity | Growing | Mature | Growing |
The entry point: use Bun as your package manager on existing Node.js projects. bun install instead of npm install — no other changes needed. From there, adopt more features as it makes sense.
Full article at stacknotice.com/blog/bun-complete-guide-2026
Top comments (0)