Bun Hot Reload
Bun provides efficient Hot Reload and Watch Mode, significantly improving development efficiency. This chapter introduces Bun's hot reload functionality.
Watch Mode
Basic Usage
Use the --watch flag to listen for file changes and automatically rerun:
bash
# Run script in watch mode
bun --watch index.ts
# Run server in watch mode
bun --watch server.tsHow It Works
┌─────────────────────────────────────────┐
│ Watch Mode │
├─────────────────────────────────────────┤
│ 1. Start application │
│ 2. Watch file system changes │
│ 3. Detect change → terminate current │
│ 4. Restart application │
│ 5. Return to step 2 │
└─────────────────────────────────────────┘Files Watched
Bun automatically watches:
- Entry files
- All imported modules
- Related config files
typescript
// index.ts
import { helper } from "./utils/helper"; // Will be watched
import config from "./config.json"; // Will be watched
import { library } from "some-package"; // node_modules not watched
console.log("Application started");Hot Mode
Hot Reload (Without Restarting Process)
Use the --hot flag to enable true hot reload:
bash
bun --hot server.tsHot Reload vs Watch Mode
| Feature | --watch | --hot |
|---|---|---|
| Process restart | Yes | No |
| State preservation | No | Yes |
| Speed | Fast | Faster |
| Use case | Scripts, CLI | HTTP Server |
| Connection keepalive | No | Yes |
HTTP Server Hot Reload
typescript
// server.ts
const server = Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello, Hot Reload!");
},
});
console.log(`Server running at http://localhost:${server.port}`);
// Export server to support hot reload
export default server;Run:
bash
bun --hot server.tsAfter modifying code, the server will hot update without restarting, and existing connections are maintained.
Configuring Watch
Additional Watched Files
bash
# Watch additional files
bun --watch index.ts --watch-file config.json
bun --watch index.ts --watch-file .envpackage.json Scripts
json
{
"scripts": {
"dev": "bun --watch src/index.ts",
"dev:hot": "bun --hot src/server.ts",
"dev:all": "bun --watch src/index.ts --watch-file .env --watch-file config.json"
}
}State Preservation
Hot Reload State
typescript
// Preserve state during hot reload
declare global {
var __hotState: Map<string, any>;
}
// Initialize global state (only on first run)
globalThis.__hotState ??= new Map();
// Use state
function getCounter(): number {
return globalThis.__hotState.get("counter") ?? 0;
}
function incrementCounter(): number {
const count = getCounter() + 1;
globalThis.__hotState.set("counter", count);
return count;
}
// Counter value will be preserved after hot reload
console.log("Current count:", incrementCounter());Exporting Default
typescript
// For HTTP servers, export default to support hot reload
const server = Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello!");
},
});
// Must export for hot reload
export default server;Practical Applications
Development Server
typescript
// dev-server.ts
const port = parseInt(Bun.env.PORT || "3000");
let requestCount = 0;
const server = Bun.serve({
port,
fetch(request) {
requestCount++;
const url = new URL(request.url);
console.log(`[${requestCount}] ${request.method} ${url.pathname}`);
// API routes
if (url.pathname.startsWith("/api/")) {
return handleApi(request);
}
// Static files
return serveStatic(url.pathname);
},
});
async function handleApi(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/api/status") {
return Response.json({
status: "ok",
requests: requestCount,
uptime: process.uptime(),
});
}
return Response.json({ error: "Not Found" }, { status: 404 });
}
async function serveStatic(pathname: string): Promise<Response> {
const filePath = `./public${pathname === "/" ? "/index.html" : pathname}`;
const file = Bun.file(filePath);
if (await file.exists()) {
return new Response(file);
}
return new Response("Not Found", { status: 404 });
}
console.log(`Development server running at http://localhost:${server.port}`);
console.log("Use --hot mode to enable hot reload");
export default server;Run:
bash
bun --hot dev-server.tsDevelopment Tools Integration
typescript
// dev.ts
import { $ } from "bun";
// Run multiple services in parallel
const processes = [
// Frontend development server
$`bun --hot src/client/dev-server.ts`.quiet(),
// Backend API server
$`bun --hot src/server/api.ts`.quiet(),
// Watch for style changes
$`bun --watch src/styles/build.ts`.quiet(),
];
console.log("Development environment started");
console.log("- Frontend: http://localhost:3000");
console.log("- API: http://localhost:3001");
// Wait for all processes
await Promise.all(processes);Testing Watch Mode
Watch Tests
bash
# Run tests in watch mode
bun test --watchTests will automatically rerun when test files change.
Run Related Tests Only
bash
# Run only tests related to changes
bun test --watch --onlyCustom Watch Logic
Using fs.watch
typescript
import { watch } from "node:fs";
// Custom watch directory
watch("./data", { recursive: true }, async (event, filename) => {
console.log(`File change: ${event} - ${filename}`);
if (filename?.endsWith(".json")) {
console.log("Reloading config...");
await reloadConfig();
}
});
async function reloadConfig() {
const config = await Bun.file("./data/config.json").json();
console.log("Config updated:", config);
}
console.log("Watching ./data directory for changes...");
// Keep process running
await Bun.sleep(Infinity);Debounce Processing
typescript
import { watch } from "node:fs";
let timeout: Timer | null = null;
function debounce(fn: () => void, delay: number) {
return () => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(fn, delay);
};
}
const rebuild = debounce(async () => {
console.log("Rebuilding...");
const result = await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
});
if (result.success) {
console.log("Build complete!");
} else {
console.error("Build failed");
}
}, 100);
watch("./src", { recursive: true }, (event, filename) => {
if (filename?.match(/\.[jt]sx?$/)) {
console.log(`Change detected: ${filename}`);
rebuild();
}
});
console.log("Watching source files for changes...");Production Environment Notes
Do Not Use in Production
typescript
// ❌ Don't do this
// bun --hot production-server.ts
// ✅ Production
// bun production-server.tsEnvironment Separation
json
{
"scripts": {
"dev": "bun --hot src/server.ts",
"start": "NODE_ENV=production bun src/server.ts"
}
}typescript
// server.ts
const isDev = Bun.env.NODE_ENV !== "production";
if (isDev) {
console.log("Development mode - hot reload enabled");
}
const server = Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello!");
},
});
export default isDev ? server : undefined;FAQ
Hot Reload Not Working
typescript
// Ensure server is exported
const server = Bun.serve({ ... });
// ❌ Missing export
// End
// ✅ Correct export
export default server;State Lost
typescript
// Use globalThis to preserve state
globalThis.__state ??= {
counter: 0,
cache: new Map(),
};
// Now state will be preserved after hot reloadSome Files Not Being Watched
bash
# Explicitly specify files to watch
bun --watch index.ts --watch-file .env --watch-file config.tomlSummary
This chapter covered:
- ✅ Watch mode (
--watch) automatic restart - ✅ Hot mode (
--hot) no-restart hot update - ✅ State preservation techniques
- ✅ Development server configuration
- ✅ Testing watch mode
- ✅ Custom watch logic
Next Steps
Continue reading SQLite Database to learn about Bun's built-in database support.