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:

# Run script in watch mode
bun --watch index.ts

# Run server in watch mode
bun --watch server.ts

How 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
// 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:

bun --hot server.ts

Hot Reload vs Watch Mode

Feature--watch--hot
Process restartYesNo
State preservationNoYes
SpeedFastFaster
Use caseScripts, CLIHTTP Server
Connection keepaliveNoYes

HTTP Server Hot Reload

// 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:

bun --hot server.ts

After modifying code, the server will hot update without restarting, and existing connections are maintained.

Configuring Watch

Additional Watched Files

# Watch additional files
bun --watch index.ts --watch-file config.json
bun --watch index.ts --watch-file .env

package.json Scripts

{
  "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

// 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

// 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

// 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:

bun --hot dev-server.ts

Development Tools Integration

// 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

# Run tests in watch mode
bun test --watch

Tests will automatically rerun when test files change.

# Run only tests related to changes
bun test --watch --only

Custom Watch Logic

Using fs.watch

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

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

// ❌ Don't do this
// bun --hot production-server.ts

// ✅ Production
// bun production-server.ts

Environment Separation

{
  "scripts": {
    "dev": "bun --hot src/server.ts",
    "start": "NODE_ENV=production bun src/server.ts"
  }
}
// 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

// Ensure server is exported
const server = Bun.serve({ ... });

// ❌ Missing export
// End

// ✅ Correct export
export default server;

State Lost

// Use globalThis to preserve state
globalThis.__state ??= {
  counter: 0,
  cache: new Map(),
};

// Now state will be preserved after hot reload

Some Files Not Being Watched

# Explicitly specify files to watch
bun --watch index.ts --watch-file .env --watch-file config.toml

Summary

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.