| name | bun-getting-started |
| description | Provides comprehensive guidance for getting started with Bun 1.3, including installation, project setup with HRM (Hot Module Replacement), database configuration with Bun.SQL (MySQL, PostgreSQL, SQLite), and best practices for building full-stack JavaScript applications. |
| tags | ["bun","javascript","full-stack","databases","development"] |
Bun 1.3 Getting Started Guide
Overview
Bun 1.3 is a batteries-included full-stack JavaScript runtime that combines a fast JavaScript runtime with built-in tooling for frontend and backend development. This guide walks through everything needed to get a new project running smoothly.
Installation
macOS (Recommended for your setup)
brew tap oven-sh/bun
brew install bun
curl -fsSL https://bun.sh/install | bash
bun --version
Other Installation Methods
npm install -g bun
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun
powershell -c "irm bun.sh/install.ps1 | iex"
Project Initialization
Create a New Project
bun init
bun init --react
bun init --react=tailwind
bun init --react=shadcn
Project Structure
A typical Bun full-stack project looks like:
my-app/
├── src/
│ ├── index.html # Frontend entry point
│ ├── App.tsx # React component
│ ├── styles.css # Frontend styles
│ └── server.ts # Backend routes
├── bun.lock # Dependency lock file
├── bunfig.toml # Bun configuration
├── tsconfig.json # TypeScript configuration
└── package.json # Dependencies & scripts
Hot Module Replacement (HMR)
What is HMR?
Hot Module Replacement lets you update your code and see changes instantly without page reloads or losing app state. Bun 1.3 includes built-in HMR support for both frontend and backend development.
Setting Up HMR in Your Server
import { serve } from "bun";
import App from "./App.html";
serve({
port: 3000,
development: {
hmr: true,
console: true,
},
routes: {
"/": App,
"/api/*": handleApi,
},
});
async function handleApi(request: Request) {
return Response.json({ message: "Hello from API" });
}
HMR Features
- React Fast Refresh: Update React components without losing state
- Browser Console Logs: See
console.log() output from browser in your terminal
- Automatic Reloading: Changes are reflected instantly
- Zero Configuration: Works out of the box with
hmr: true
Client-Side HMR API
For custom HMR behavior:
if (import.meta.hot) {
import.meta.hot.accept(() => {
console.log("Module updated!");
});
import.meta.hot.dispose(() => {
console.log("Cleaning up old module");
});
}
Database Setup with Bun.SQL
Overview
Bun.SQL provides a unified API for MySQL, PostgreSQL, and SQLite with zero dependencies. It's incredibly fast and type-safe.
1. PostgreSQL Setup
Installation & Connection
brew install postgresql
brew services start postgresql
createdb myapp_db
docker run --name postgres -e POSTGRES_PASSWORD=password -d postgres
Usage in Bun
import { sql } from "bun";
const db = sql;
import { SQL } from "bun";
const db = new SQL("postgres://localhost/mydb");
const users = await sql`SELECT * FROM users LIMIT 10`;
const age = 65;
const seniors = await sql`
SELECT name, age FROM users
WHERE age >= ${age}
`;
const [user] = await sql`
INSERT INTO users (name, email)
VALUES (${"Alice"}, ${"alice@example.com"})
RETURNING *
`;
await sql`
UPDATE users
SET name = ${"Bob"}
WHERE id = ${userId}
`;
await sql`
INSERT INTO users (name, roles)
VALUES (${"Alice"}, ${sql.array(["admin", "user"], "TEXT")})
`;
Advanced PostgreSQL Features
await sql`
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
);
CREATE INDEX idx_users_email ON users(email);
INSERT INTO users (name, email) VALUES ('Admin', 'admin@example.com');
`.simple();
const db = new SQL({
path: "/tmp/.s.PGSQL.5432",
user: "postgres",
password: "postgres",
database: "mydb"
});
const db = new SQL({
prepare: false,
});
const user = { name: "Alice", email: "alice@example.com", age: 30 };
await sql`INSERT INTO users ${sql(user, "name", "email")}`;
const updates = { name: "Alice Smith", email: "alice.smith@example.com" };
await sql`UPDATE users SET ${sql(updates)} WHERE id = ${userId}`;
2. MySQL Setup
Installation & Connection
brew install mysql
brew services start mysql
mysql -u root -e "CREATE DATABASE myapp_db;"
docker run --name mysql -e MYSQL_ROOT_PASSWORD=password -d mysql
Usage in Bun
import { SQL } from "bun";
const db = new SQL("mysql://root:password@localhost/myapp_db");
const users = await db`SELECT * FROM users LIMIT 10`;
const [user] = await db`
INSERT INTO users (name, email)
VALUES (${"Bob"}, ${"bob@example.com"})
`;
3. SQLite Setup
Installation & Usage
SQLite is the easiest option—just a file on disk, no server needed.
import { SQL } from "bun";
const db = new SQL("sqlite://data.db");
const users = await db`SELECT * FROM users`;
import { Database } from "bun:sqlite";
const serialized = db.serialize();
const deserialized = Database.deserialize(serialized, {
readonly: true,
strict: true,
safeIntegers: true,
});
const stmt = db.query("SELECT * FROM users");
console.log(stmt.declaredTypes);
console.log(stmt.columnTypes);
Choosing Your Database
| Database | Best For | Setup Complexity |
|---|
| PostgreSQL | Production servers, advanced features (arrays, JSON), multi-user | Medium |
| MySQL | Production servers, web apps, reliability | Medium |
| SQLite | Development, single-file apps, mobile, embedded | Easy |
Database Connection Patterns
Environment-Based Configuration
[install]
# Automatically load from DATABASE_URL
# export DATABASE_URL="postgres://localhost/mydb"
import { sql } from "bun";
const result = await sql`SELECT version()`;
Connection Pooling
import { SQL } from "bun";
const db = new SQL("postgres://localhost/mydb", {
max: 20,
});
Building Full-Stack Apps
Basic Full-Stack Example
import { serve } from "bun";
import { sql } from "bun";
import App from "./App.html";
serve({
port: 3000,
development: {
hmr: true,
console: true,
},
routes: {
"/*": App,
"/api/users": {
GET: async () => {
const users = await sql`SELECT * FROM users LIMIT 10`;
return Response.json(users);
},
POST: async (req) => {
const { name, email } = await req.json();
const [user] = await sql`
INSERT INTO users ${sql({ name, email })}
RETURNING *
`;
return Response.json(user);
},
},
"/api/users/:id": async (req) => {
const { id } = req.params;
const [user] = await sql`
SELECT * FROM users WHERE id = ${id} LIMIT 1
`;
if (!user) {
return new Response("User not found", { status: 404 });
}
return Response.json(user);
},
"/healthcheck.json": Response.json({ status: "ok" }),
},
});
Cookies
import { serve, randomUUIDv7 } from "bun";
serve({
routes: {
"/api/login": (request) => {
request.cookies.set("sessionId", randomUUIDv7(), {
httpOnly: true,
sameSite: "strict",
maxAge: 60 * 60 * 24 * 7,
});
return new Response("Logged in");
},
"/api/logout": (request) => {
request.cookies.delete("sessionId");
return new Response("Logged out");
},
"/api/profile": (request) => {
const sessionId = request.cookies.get("sessionId");
if (!sessionId) {
return new Response("Not authenticated", { status: 401 });
}
return Response.json({ session: sessionId });
},
},
});
WebSockets
import { serve } from "bun";
serve({
routes: {
"/ws": {
fetch(req) {
if (req.headers.get("upgrade") === "websocket") {
return new Response(null, {
status: 101,
webSocket: {
open(ws) {
console.log("Client connected");
ws.send(JSON.stringify({ message: "Welcome!" }));
},
message(ws, message) {
console.log("Received:", message);
ws.send(JSON.stringify({ echo: message }));
},
close(ws) {
console.log("Client disconnected");
},
},
});
}
return new Response("Not a WebSocket request", { status: 400 });
},
},
},
});
Package Management with Bun
Installing Dependencies
bun install
bun add express
bun add -d @types/node
bun add react@18.3.1
bun add --optional webpack
Workspace Monorepos
{
"name": "monorepo",
"workspaces": ["packages/*"],
"catalogs": {
"react": "^18.0.0",
"typescript": "^5.0.0",
"tailwindcss": "^3.0.0"
}
}
In workspace packages, reference catalog versions:
{
"name": "@company/frontend",
"dependencies": {
"react": "catalog:react",
"tailwindcss": "catalog:tailwindcss"
}
}
Useful Commands
bun why react
bun update --interactive
bun update --interactive --filter @company/frontend
bun audit
TypeScript Configuration
Default Setup
{
"compilerOptions": {
"module": "Preserve",
"lib": ["ESNext"],
"target": "ESNext",
"moduleResolution": "bundler",
"allowJs": true,
"jsx": "react-jsx"
}
}
Environment Types
Bun automatically detects whether to use Node.js or DOM types. Override if needed:
{
"compilerOptions": {
"types": ["bun-types"]
}
}
Building for Production
Create a Standalone Executable
bun build --compile ./server.ts --outfile myapp
bun build --compile --target=linux-x64 ./server.ts --outfile myapp-linux
./myapp
Environment Variables
DATABASE_URL="postgres://user:pass@localhost/mydb"
NODE_ENV="production"
PORT="8080"
const dbUrl = process.env.DATABASE_URL;
Testing
Basic Test Setup
bun test
import { test, expect } from "bun:test";
test("addition", () => {
expect(1 + 1).toBe(2);
});
test("async operations", async () => {
const result = await Promise.resolve(42);
expect(result).toBe(42);
});
test.concurrent("fetch users", async () => {
const res = await fetch("/api/users");
expect(res.status).toBe(200);
});
Performance Tips
- Use Prepared Statements: Bun.SQL uses them by default (safest)
- Connection Pooling: Configure
max option for database connections
- Lazy Load Routes: Use dynamic imports for rarely-used features
- Cache Static Assets: Let Bun serve them directly
- Monitor Memory: Use
--console-depth for debugging
- Database Preconnection: Use
--sql-preconnect flag to reduce first-query latency
export DATABASE_URL="postgres://localhost/mydb"
bun --sql-preconnect ./server.ts
Common Patterns
API Response Wrapper
function apiResponse<T>(data: T, status = 200) {
return Response.json({ data, status }, { status });
}
function apiError(message: string, status = 400) {
return Response.json({ error: message, status }, { status });
}
Database Migrations
import { sql } from "bun";
export async function migrate() {
await sql`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
`.simple();
console.log("✓ Migrations complete");
}
await migrate();
Error Handling
try {
const [user] = await sql`
INSERT INTO users (email) VALUES (${email})
`;
return Response.json(user);
} catch (error) {
if (error instanceof Error) {
if (error.message.includes("unique")) {
return apiError("Email already exists", 409);
}
}
console.error(error);
return apiError("Internal server error", 500);
}
Troubleshooting
Database Connection Issues
echo "SELECT 1" | psql postgres://user:pass@localhost/dbname
echo $DATABASE_URL
export BUN_CONFIG_VERBOSE_FETCH=curl
Hot Reload Not Working
- Ensure
hmr: true is set in Bun.serve()
- Check that
development object is properly configured
- Verify file changes are being saved to disk
- Clear browser cache or open in private/incognito mode
Module Not Found Errors
bun install
bun why package-name
rm -rf node_modules bun.lock
bun install
Resources
Next Steps
- Create your first project:
bun init --react
- Set up your database connection
- Build API routes with
Bun.serve()
- Deploy using
bun build --compile
- Monitor performance and optimize as needed
Version History
- v1.0 - Initial guide for Bun 1.3 release
- Includes HMR setup, Bun.SQL for all three databases, full-stack examples