Bun.js Complete Guide — TypeScript Runtime Faster Than Node.js 2024
Advertisement
Introduction
Why This Matters
Bun is not just another JavaScript runtime — it is a complete reimagining of the Node.js toolchain. Bun combines a runtime (replacing Node.js), a package manager (replacing npm/pnpm), a bundler (replacing esbuild/webpack), and a test runner (replacing Vitest/Jest) into a single binary with no configuration required.
For TypeScript backend developers, Bun's most significant advantage is that it runs TypeScript files natively — no compilation step, no ts-node, no tsx. You write .ts files and execute them directly. In benchmarks, Bun's HTTP server handles 3-4x more requests per second than Node.js with similar code.
In 2024, Bun is production-ready and increasingly adopted for new greenfield projects, serverless functions, and anywhere startup time and throughput are critical.
Installation
# Install Bun
curl -fsSL https://bun.sh/install | bash
# Verify installation
bun --version
# Create a new project
mkdir my-api && cd my-api
bun init -y
# Run TypeScript directly
bun run src/index.tsBun HTTP Server
Bun's built-in HTTP server is faster than both Node.js HTTP and Express.
// src/index.ts — no framework needed for simple APIs
const server = Bun.serve({
port: 3000,
hostname: '0.0.0.0',
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/health' && request.method === 'GET') {
return Response.json({ status: 'ok', timestamp: new Date().toISOString() });
}
if (url.pathname === '/api/users' && request.method === 'GET') {
const users = await db.query('SELECT id, name, email FROM users').all();
return Response.json({ users });
}
if (url.pathname === '/api/users' && request.method === 'POST') {
const body = await request.json() as { name: string; email: string };
const result = db.run(
'INSERT INTO users (name, email) VALUES (?, ?)',
[body.name, body.email]
);
return Response.json({ id: result.lastInsertRowid }, { status: 201 });
}
return new Response('Not Found', { status: 404 });
},
error(error: Error): Response {
console.error('[Server Error]', error);
return new Response('Internal Server Error', { status: 500 });
},
});
console.log(`Bun server running on http://localhost:${server.port}`);Built-in SQLite
Bun ships with SQLite bindings — no native module compilation, no pg or mysql2 required for simple use cases.
import { Database } from 'bun:sqlite';
const db = new Database('app.db');
// Enable WAL mode for better concurrency
db.run('PRAGMA journal_mode = WAL');
db.run('PRAGMA synchronous = NORMAL');
// Create table
db.run(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
interface User {
id: number;
name: string;
email: string;
created_at: string;
}
// Prepared statements for performance
const findUserById = db.prepare<User, [number]>('SELECT * FROM users WHERE id = ?');
const insertUser = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
const listUsers = db.prepare<User, []>('SELECT * FROM users ORDER BY created_at DESC');
// Usage
const user = findUserById.get(1); // User | null
const allUsers = listUsers.all(); // User[]
const result = insertUser.run('Alice', 'alice@example.com');
console.log(result.lastInsertRowid); // numberBun with Hono (Recommended Framework)
For production APIs, use Hono with Bun.
import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { Database } from 'bun:sqlite';
const db = new Database('app.db');
const app = new Hono();
app.use('*', logger());
app.use('/api/*', cors());
const createUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
app.post(
'/api/users',
zValidator('json', createUserSchema),
async (c) => {
const { name, email } = c.req.valid('json');
const stmt = db.prepare('INSERT INTO users (name, email) VALUES (?, ?) RETURNING *');
const user = stmt.get(name, email);
return c.json(user, 201);
}
);
export default {
port: parseInt(process.env.PORT ?? '3000'),
fetch: app.fetch,
};File I/O
Bun's file API is considerably faster than Node.js's fs module.
// Read a file
const file = Bun.file('config.json');
const config = await file.json<{ port: number; host: string }>();
// Write a file
await Bun.write('output.txt', 'Hello from Bun!');
// Write JSON
await Bun.write('data.json', JSON.stringify({ key: 'value' }, null, 2));
// Stream a large file
const stream = Bun.file('large-file.csv').stream();
for await (const chunk of stream) {
// process chunk
}Built-in Test Runner
// src/users.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'bun:test';
import { Database } from 'bun:sqlite';
describe('User service', () => {
let db: Database;
beforeAll(() => {
db = new Database(':memory:');
db.run('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');
});
afterAll(() => db.close());
it('inserts a user', () => {
const stmt = db.prepare('INSERT INTO users (name, email) VALUES (?, ?) RETURNING *');
const user = stmt.get('Alice', 'alice@example.com') as { id: number; name: string };
expect(user.name).toBe('Alice');
expect(user.id).toBeGreaterThan(0);
});
});# Run tests
bun test
# Watch mode
bun test --watch
# Coverage
bun test --coverageNode.js Compatibility
Bun is ~95% compatible with the Node.js API and npm ecosystem.
// Works in Bun unchanged
import fs from 'fs/promises';
import path from 'path';
import { createServer } from 'http';
import crypto from 'crypto';
// npm packages work too
import express from 'express'; // works but slower than Bun.serve
import { z } from 'zod'; // works perfectly
import pg from 'pg'; // works for PostgreSQLCommon Mistakes
- Using
node:fswhenBun.file()is 5-10x faster for the same operation - Expecting 100% Node.js API compatibility — some native modules still need Node.js
- Not using prepared statements in
bun:sqlite— ad-hoc queries are slower - Using
console.logfor request logging instead of a structured logger - Not setting
Bun.serveerror handler — unhandled errors become empty 500 responses
Best Practices
- Use
bun:sqlitewith WAL mode for local/embedded database needs - Use Hono or ElysiaJS as your framework — they are optimized for Bun's fetch-based server model
- Use
bun testinstead of Vitest — the API is compatible and Bun's runner is faster - Use
bun buildfor compiling TypeScript to a single executable for deployment - Profile with
bun --smolfor memory-constrained environments
Key Takeaways
- Bun runs TypeScript natively — no compilation step or transpiler configuration required
Bun.serve()handles ~120,000 req/sec on typical hardware vs ~40,000 for Node.js + Expressbun:sqliteprovides synchronous, high-performance SQLite access with no native module setup- Bun is a drop-in npm replacement —
bun installis 20-100x faster thannpm install bun testis compatible with Jest and Vitest test APIs and requires zero configurationBun.file()reads and writes files 5-10x faster than Node.jsfs.promises- Bun supports ~95% of the Node.js API — most Express and Fastify apps run without modification
bun buildcompiles TypeScript apps to standalone executables for serverless and edge deployment
Advertisement