Bun Runtime Migration: Porting High-Traffic Node.js APIs with Native APIs and SQLite
Bun Runtime Migration: Porting High-Traffic Node.js APIs with Native APIs and SQLite
Bun delivers 4× HTTP throughput and significant SQLite performance gains over Node.js. This guide covers migrating high-traffic APIs using Bun's native APIs for maximum performance.
Core Runtime Migration
Replace Express/Fastify with Bun.serve for native fetch-based HTTP handling.
Node.js Express pattern:
import express from 'express';
const app = express();
app.use(express.json());
app.get('/users', (req, res) => {
res.json({ users: [] });
});
app.listen(3000);
Bun native equivalent:
Bun.serve({
port: 3000,
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === '/users') {
return Response.json({ users: [] });
}
return new Response('Not Found', { status: 404 });
},
});
Performance gain: Bun 1.1.20 handles ~52,000 requests/second vs Node.js 20.15.0's 13,000 in HTTP benchmarks (tested on 8-core Linux with autocannon, 100 concurrent connections, simple JSON response).
Native API Integration
Replace external dependencies with Bun's built-in globals.
Password Hashing
Replace bcrypt with Bun.password:
// Node.js
import bcrypt from 'bcrypt';
const hash = await bcrypt.hash(password, 10);
// Bun
try {
const hash = await Bun.password.hash(password);
const match = await Bun.password.verify(password, hash);
} catch (error) {
console.error('Password hashing failed:', error);
}
File I/O
Replace fs with Bun.file:
// Node.js
import { readFile } from 'fs/promises';
const content = await readFile('./data.json', 'utf8');
// Bun
try {
const file = Bun.file('./data.json');
const content = await file.text();
} catch (error) {
console.error('File read failed:', error);
}
Database Layer: bun:sqlite
Use bun:sqlite for synchronous, high-performance SQLite operations. This is a built-in module requiring no installation.
Basic usage with WAL mode:
import { Database } from 'bun:sqlite';
const db = new Database('app.db');
// Enable WAL mode for concurrent reads/writes
db.exec('PRAGMA journal_mode = WAL;');
db.exec('PRAGMA synchronous = NORMAL;');
// Create table
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
email TEXT UNIQUE,
name TEXT
)
`);
// Insert with prepared statement
const insert = db.prepare('INSERT INTO users (email, name) VALUES (?, ?)');
insert.run('user@example.com', 'John Doe');
// Query data
const users = db.prepare('SELECT * FROM users WHERE id = ?').all(1);
High-traffic considerations: bun:sqlite is synchronous and runs on a single connection. WAL mode enables concurrent reads while serializing writes. For write-heavy workloads, consider batching operations within transactions. Unlike Node.js async drivers, there's no connection pooling overhead.
High-traffic transaction handling with db.transaction():
// Batch insert with transaction for better performance
const insert = db.prepare('INSERT INTO users (email, name) VALUES (?, ?)');
// Define users data
const users = [
{ email: 'alice@example.com', name: 'Alice' },
{ email: 'bob@example.com', name: 'Bob' },
{ email: 'charlie@example.com', name: 'Charlie' },
];
// Use db.transaction() for automatic commit/rollback
const batchInsert = db.transaction((users) => {
for (const user of users) {
insert.run(user.email, user.name);
}
});
try {
batchInsert(users);
console.log('Batch insert completed');
} catch (error) {
console.error('Batch insert failed:', error);
}
The db.transaction() method wraps operations in a transaction that automatically commits on success or rolls back on error.
Performance comparison (Bun 1.1.20 vs Node.js 20.15.0 with better-sqlite3, complex transactions with indexes and WAL mode):
- Bun: 81,370 queries/second
- Node.js (better-sqlite3): 21,290 queries/second
- Speedup: 3.8× faster
Simple CRUD operations typically exceed 100,000 queries/second on both runtimes.
Complete API Migration Example
Before (Node.js + Express + better-sqlite3):
import express from 'express';
import Database from 'better-sqlite3';
import bcrypt from 'bcrypt';
const app = express();
const db = new Database('app.db');
const PORT = 3000;
app.use(express.json());
app.post('/register', async (req, res) => {
try {
const { email, password, name } = req.body;
if (!email || !password || !name) {
return res.status(400).json({ error: 'Missing required fields' });
}
const hash = await bcrypt.hash(password, 10);
const stmt = db.prepare('INSERT INTO users (email, password, name) VALUES (?, ?, ?)');
stmt.run(email, hash, name);
res.json({ success: true });
} catch (error) {
console.error('Registration failed:', error);
res.status(500).json({ error: 'Registration failed' });
}
});
app.listen(PORT);
After (Bun native with TypeScript):
import { Database } from 'bun:sqlite';
interface User {
id: number;
email: string;
password: string;
name: string;
}
const db = new Database('app.db');
db.exec('PRAGMA journal_mode = WAL;');
db.exec('PRAGMA synchronous = NORMAL;');
const server = Bun.serve({
port: 3000,
maxRequestBodySize: 1024 * 1024, // 1MB limit for high-traffic protection
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === '/register' && request.method === 'POST') {
try {
const body = await request.json() as Partial<User>;
const { email, password, name } = body;
if (!email || !password || !name) {
return Response.json({ error: 'Missing required fields' }, { status: 400 });
}
const hash = await Bun.password.hash(password);
const stmt = db.prepare('INSERT INTO users (email, password, name) VALUES (?, ?, ?)');
stmt.run(email, hash, name);
return Response.json({ success: true });
} catch (error) {
console.error('Registration failed:', error);
return Response.json({ error: 'Registration failed' }, { status: 500 });
}
}
return new Response('Not Found', { status: 404 });
},
});
// Graceful shutdown
process.on('SIGINT', () => {
console.log('Shutting down gracefully...');
db.close();
server.stop();
Bun.exit(0);
});
process.on('SIGTERM', () => {
console.log('Shutting down gracefully...');
db.close();
server.stop();
Bun.exit(0);
});
TypeScript benefits: Bun natively supports TypeScript without configuration. The .ts extension provides type safety for request bodies, database results, and API responses. Use bun build --compile ./src/index.ts to compile directly from TypeScript to a standalone binary.
Development Workflow
Hot reloading for development:
bun --hot src/index.ts
The --hot flag automatically restarts the server on file changes, enabling rapid iteration during migration.
npm Package Compatibility
Most npm packages work unchanged in Bun. However, some packages may require attention:
- Native modules: Packages with native bindings (e.g.,
sharp,bcrypt) may need Bun-specific versions or replacement with Bun's built-in alternatives (Bun.passwordreplaces bcrypt). - Node.js-specific APIs: Packages using
process.nextTick,setImmediate, or internal Node APIs usually work but should be tested. - Testing frameworks: Jest migrations should use
bun testfor faster execution with a compatible API.
Run bun install to install dependencies. Bun's package manager is significantly faster than npm, typically completing in milliseconds.
Production Deployment
Environment variables:
const PORT = Bun.env.PORT || 3000;
const DB_PATH = Bun.env.DB_PATH || './app.db';
Binary compilation:
bun build --compile ./src/index.ts --outfile myapp
Optimized Dockerfile:
FROM oven/bun:1.1.20 AS builder
WORKDIR /app
COPY package.json bun.lockb ./
COPY src ./src
RUN bun build --compile ./src/index.ts --outfile /app/myapp
FROM alpine:latest
COPY --from=builder /app/myapp /app/myapp
EXPOSE 3000
CMD ["/app/myapp"]
Migration Checklist
- Test compatibility: Migrate Jest tests to
bun test(similar API, faster execution) - Replace HTTP layer: Convert Express routes to
Bun.servehandlers - Migrate dependencies: Replace bcrypt →
Bun.password, fs →Bun.file - Database migration: Switch to
bun:sqlitewith WAL mode enabled - Add transaction handling: Use
db.transaction()for batch operations - Set body limits: Configure
maxRequestBodySizefor high-traffic protection - Implement graceful shutdown: Handle SIGINT/SIGTERM with
Bun.exit() - Performance validation: Benchmark critical endpoints
- Shadow deployment: Mirror traffic before full cutover
Expected gains: 3-4× request throughput, 2× faster cold starts, 3.8× database query performance.
Share this Guide:
More Guides
Agentic Workflows: Building Self-Correcting Loops with LangGraph and CrewAI State Machines
Build production-ready AI agents that iteratively improve their outputs through automated feedback loops, combining LangGraph's state machine architecture with CrewAI's multi-agent orchestration for robust, self-correcting workflows.
14 min readDeno 2.0 Workspaces: Build Monorepos with JSR Packages and TypeScript-First Development
Learn how to configure Deno 2.0 workspaces for monorepo management, publish TypeScript packages to JSR, and automate releases with OIDC-authenticated CI/CD pipelines.
7 min readGleam on BEAM: Building Type-Safe, Fault-Tolerant Distributed Systems
Learn how Gleam combines Hindley-Milner type inference with Erlang's actor-based concurrency model to build systems that are both compile-time safe and runtime fault-tolerant. Covers OTP integration, supervision trees, and seamless interoperability with the BEAM ecosystem.
5 min readHono Edge Framework: Build Ultra-Fast APIs for Cloudflare Workers and Bun
Master Hono's zero-dependency web framework to build low-latency edge APIs that deploy seamlessly across Cloudflare Workers, Bun, and other JavaScript runtimes. Learn routing, middleware, validation, and real-time streaming patterns optimized for edge computing.
6 min readLLM Observability: OpenTelemetry Tracing for Non-Deterministic AI Chains
Master OpenTelemetry tracing for LLM workflows with semantic conventions, token metrics, and non-deterministic chain monitoring for production AI systems.
9 min readContinue Reading
Agentic Workflows: Building Self-Correcting Loops with LangGraph and CrewAI State Machines
Build production-ready AI agents that iteratively improve their outputs through automated feedback loops, combining LangGraph's state machine architecture with CrewAI's multi-agent orchestration for robust, self-correcting workflows.
14 min readDeno 2.0 Workspaces: Build Monorepos with JSR Packages and TypeScript-First Development
Learn how to configure Deno 2.0 workspaces for monorepo management, publish TypeScript packages to JSR, and automate releases with OIDC-authenticated CI/CD pipelines.
7 min readGleam on BEAM: Building Type-Safe, Fault-Tolerant Distributed Systems
Learn how Gleam combines Hindley-Milner type inference with Erlang's actor-based concurrency model to build systems that are both compile-time safe and runtime fault-tolerant. Covers OTP integration, supervision trees, and seamless interoperability with the BEAM ecosystem.
5 min readReady to Supercharge Your Development Workflow?
Join thousands of engineering teams using MatterAI to accelerate code reviews, catch bugs earlier, and ship faster.
