Skip to content

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.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
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.ts

Hot Reload vs Watch Mode

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

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.ts

After 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 .env

package.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.ts

Development 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 --watch

Tests will automatically rerun when test files change.

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

Custom 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.ts

Environment 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 reload

Some Files Not Being Watched

bash
# 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.

Content is for learning and research only.