home / skills / daleseo / bun-skills / bun-dev-server

bun-dev-server skill

/skills/bun-dev-server

This skill helps you set up a high-performance Bun development server with hot module replacement and React Fast Refresh for frontend, API, or full-stack apps.

npx playbooks add skill daleseo/bun-skills --skill bun-dev-server

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
11.3 KB
---
name: bun-dev-server
description: Set up high-performance development servers with Hot Module Replacement. Use when creating dev servers for web apps, setting up React Fast Refresh, or configuring API servers with live reload.
compatibility: Requires Bun 1.0+
allowed-tools: ["Bash", "Write", "Read"]
metadata:
  author: dale
  category: bun-runtime
  tags: [bun, dev-server, hmr, react, hot-reload]
---

# Bun Development Server Setup

You are assisting with setting up a high-performance development server using Bun.serve with Hot Module Replacement (HMR) and React Fast Refresh.

## Workflow

### 1. Determine Server Type

Ask the user what type of development server they need:

- **React/Frontend App**: SPA with React Fast Refresh
- **API Server**: REST/GraphQL API with auto-reload
- **Full-Stack App**: Frontend + API combined
- **Static Server**: File server with live reload

### 2. Check Prerequisites

```bash
# Verify Bun installation
bun --version

# Check if project has package.json
ls -la package.json
```

If no package.json exists, suggest running `bun init` first.

### 3. Install Dependencies

**For React Apps:**
```bash
bun add react react-dom
bun add -d @types/react @types/react-dom
```

**For API with Hono (recommended):**
```bash
bun add hono
```

**For Full-Stack:**
```bash
bun add react react-dom hono
bun add -d @types/react @types/react-dom
```

### 4. Create Server Configuration

#### React Development Server

Create `server.ts` in the project root:

```typescript
import type { ServerWebSocket } from "bun";

const clients = new Set<ServerWebSocket<unknown>>();

const server = Bun.serve({
  port: 3000,

  async fetch(request, server) {
    const url = new URL(request.url);

    // WebSocket for HMR
    if (url.pathname === "/_hmr") {
      const upgraded = server.upgrade(request);
      if (upgraded) return undefined;
      return new Response("WebSocket upgrade failed", { status: 500 });
    }

    // Serve index.html for SPA routing
    if (url.pathname === "/" || !url.pathname.includes(".")) {
      return new Response(
        Bun.file("public/index.html"),
        { headers: { "Content-Type": "text/html" } }
      );
    }

    // Serve static files
    const filePath = `public${url.pathname}`;
    const file = Bun.file(filePath);

    if (await file.exists()) {
      return new Response(file);
    }

    return new Response("Not Found", { status: 404 });
  },

  websocket: {
    open(ws) {
      clients.add(ws);
      console.log("HMR client connected");
    },

    close(ws) {
      clients.delete(ws);
      console.log("HMR client disconnected");
    },

    message(ws, message) {
      // Handle client messages if needed
    },
  },
});

console.log(`šŸš€ Dev server running at http://localhost:${server.port}`);

// Watch for file changes
const watcher = Bun.file.watch(import.meta.dir + "/src", {
  recursive: true,
});

for await (const event of watcher) {
  if (event.kind === "change" && event.path.endsWith(".tsx")) {
    console.log(`šŸ“ File changed: ${event.path}`);

    // Notify all connected clients to reload
    for (const client of clients) {
      client.send(JSON.stringify({ type: "reload" }));
    }
  }
}
```

Create `public/index.html`:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Bun + React App</title>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="/src/index.tsx"></script>

  <!-- HMR Client -->
  <script>
    const ws = new WebSocket('ws://localhost:3000/_hmr');

    ws.addEventListener('message', (event) => {
      const data = JSON.parse(event.data);

      if (data.type === 'reload') {
        console.log('šŸ”„ Reloading...');
        window.location.reload();
      }
    });

    ws.addEventListener('close', () => {
      console.log('āŒ HMR connection lost. Reconnecting...');
      setTimeout(() => window.location.reload(), 1000);
    });
  </script>
</body>
</html>
```

Create `src/index.tsx`:

```typescript
import { render } from 'react-dom';
import App from './App';

const root = document.getElementById('root');
render(<App />, root);
```

Create `src/App.tsx`:

```typescript
export default function App() {
  return (
    <div>
      <h1>Welcome to Bun + React!</h1>
      <p>Edit src/App.tsx to see HMR in action</p>
    </div>
  );
}
```

#### API Server with Hono

Create `server.ts`:

```typescript
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';

const app = new Hono();

// Middleware
app.use('*', cors());
app.use('*', logger());

// Routes
app.get('/', (c) => {
  return c.json({ message: 'Welcome to Bun API' });
});

app.get('/api/health', (c) => {
  return c.json({ status: 'ok', timestamp: Date.now() });
});

// Example POST endpoint
app.post('/api/users', async (c) => {
  const body = await c.req.json();
  return c.json({ created: true, data: body }, 201);
});

// Start server
const server = Bun.serve({
  port: process.env.PORT || 3000,
  fetch: app.fetch,
});

console.log(`šŸš€ API server running at http://localhost:${server.port}`);
```

#### Full-Stack Server

Create `server.ts`:

```typescript
import { Hono } from 'hono';
import { serveStatic } from 'hono/bun';

const app = new Hono();

// API routes
const api = new Hono();

api.get('/health', (c) => c.json({ status: 'ok' }));
api.get('/users', (c) => c.json({ users: [] }));

app.route('/api', api);

// Serve static files
app.use('/*', serveStatic({ root: './public' }));

// SPA fallback
app.get('*', (c) => c.html(Bun.file('public/index.html')));

const server = Bun.serve({
  port: 3000,
  fetch: app.fetch,
});

console.log(`šŸš€ Full-stack server at http://localhost:${server.port}`);
```

### 5. Configure React Fast Refresh (Advanced)

For true React Fast Refresh, create `hmr-runtime.ts`:

```typescript
// React Fast Refresh runtime
let timeout: Timer | null = null;

export function refresh() {
  if (timeout) clearTimeout(timeout);

  timeout = setTimeout(() => {
    // Re-import the App component
    import('./App.tsx?t=' + Date.now()).then((module) => {
      const { render } = require('react-dom');
      const root = document.getElementById('root');
      render(module.default(), root);
    });
  }, 100);
}

// Listen for HMR events
if (import.meta.hot) {
  import.meta.hot.accept(() => {
    refresh();
  });
}
```

### 6. Environment Configuration

Create `.env.development`:

```bash
# Server
PORT=3000
NODE_ENV=development

# API
API_URL=http://localhost:3000/api

# Features
ENABLE_HMR=true
```

Create `.env.production`:

```bash
# Server
PORT=8080
NODE_ENV=production

# API
API_URL=https://api.example.com

# Features
ENABLE_HMR=false
```

Load environment in `server.ts`:

```typescript
// Environment is loaded automatically by Bun
const isDev = process.env.NODE_ENV === 'development';
const port = process.env.PORT || 3000;
```

### 7. Update package.json Scripts

Add development scripts:

```json
{
  "scripts": {
    "dev": "bun run --hot server.ts",
    "dev:watch": "bun run --watch server.ts",
    "start": "NODE_ENV=production bun run server.ts",
    "build": "bun build src/index.tsx --outdir=dist --minify",
    "clean": "rm -rf dist"
  }
}
```

**Script explanations:**
- `dev`: Run with hot reload (restarts on file changes)
- `dev:watch`: Watch mode (faster, but doesn't reload on crash)
- `start`: Production mode
- `build`: Build frontend for production

### 8. Configure TypeScript

Update `tsconfig.json`:

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "types": ["bun-types"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "allowImportingTsExtensions": true,
    "noEmit": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*", "server.ts"]
}
```

### 9. Create Project Structure

Generate complete project structure:

```
project/
ā”œā”€ā”€ server.ts              # Development server
ā”œā”€ā”€ src/
│   ā”œā”€ā”€ index.tsx         # App entry point
│   ā”œā”€ā”€ App.tsx           # Main component
│   ā”œā”€ā”€ components/       # React components
│   └── styles/           # CSS files
ā”œā”€ā”€ public/
│   ā”œā”€ā”€ index.html        # HTML template
│   └── assets/           # Static assets
ā”œā”€ā”€ .env.development
ā”œā”€ā”€ .env.production
ā”œā”€ā”€ package.json
ā”œā”€ā”€ tsconfig.json
└── README.md
```

### 10. Advanced: HTTPS for Local Development

For HTTPS support (needed for some browser APIs):

```typescript
import { readFileSync } from 'fs';

const server = Bun.serve({
  port: 3000,
  tls: {
    cert: readFileSync('./localhost.pem'),
    key: readFileSync('./localhost-key.pem'),
  },
  fetch: app.fetch,
});

console.log(`šŸ”’ HTTPS server at https://localhost:${server.port}`);
```

Generate certificates with:
```bash
# Install mkcert first: brew install mkcert
mkcert -install
mkcert localhost 127.0.0.1 ::1
```

### 11. Proxy Configuration (for existing backends)

If user needs to proxy API requests to another server:

```typescript
const app = new Hono();

// Proxy /api requests to backend
app.all('/api/*', async (c) => {
  const url = new URL(c.req.url);
  const backendUrl = `http://localhost:8080${url.pathname}${url.search}`;

  const response = await fetch(backendUrl, {
    method: c.req.method,
    headers: c.req.raw.headers,
    body: c.req.method !== 'GET' ? await c.req.raw.text() : undefined,
  });

  return new Response(response.body, {
    status: response.status,
    headers: response.headers,
  });
});
```

## Testing the Setup

After creation, guide user to test:

```bash
# 1. Start dev server
bun run dev

# 2. Open browser
open http://localhost:3000

# 3. Make a change to src/App.tsx
# 4. Verify HMR reloads the page

# 5. Test API endpoints
curl http://localhost:3000/api/health
```

## Troubleshooting

### HMR not working

```typescript
// Check if WebSocket connection is established
// Open browser console and look for:
// "HMR client connected"

// If not, verify:
// 1. Port is correct
// 2. No firewall blocking WebSocket
// 3. Server is running with --hot flag
```

### Port already in use

```bash
# Find process using port 3000
lsof -ti:3000

# Kill the process
kill -9 $(lsof -ti:3000)

# Or use a different port
PORT=3001 bun run dev
```

### CORS issues

Add CORS headers to server:

```typescript
app.use('*', cors({
  origin: 'http://localhost:3000',
  credentials: true,
}));
```

## Performance Tips

1. **Use --hot for development**: Faster than --watch for most cases
2. **Minimize file watcher scope**: Watch only src/ directory
3. **Use HTTP/2**: Enable for faster parallel loading
4. **Cache static assets**: Add Cache-Control headers

```typescript
app.use('/assets/*', async (c, next) => {
  await next();
  c.header('Cache-Control', 'public, max-age=31536000');
});
```

## Completion Checklist

- āœ… Development server created
- āœ… HMR configured and tested
- āœ… Environment variables set up
- āœ… Package.json scripts added
- āœ… Project structure organized
- āœ… TypeScript configured
- āœ… Browser successfully connects
- āœ… File changes trigger reload

## Next Steps

Suggest to the user:
1. Add error boundaries for better error handling
2. Set up ESLint and Prettier
3. Configure path aliases in tsconfig.json
4. Add development vs production builds
5. Consider adding bun-test for testing

Overview

This skill sets up a high-performance Bun development server with Hot Module Replacement (HMR) and optional React Fast Refresh. It provides ready-made server templates for React frontends, Hono-based APIs, and full-stack apps, plus scripts, TypeScript config, and env setups to get you running quickly.

How this skill works

The skill generates a Bun.serve server that serves static assets, upgrades a WebSocket endpoint for HMR, and watches the src directory for file changes to notify connected clients. For APIs it uses Hono to define routes and middleware, and for full-stack projects it combines static serving with API routing and SPA fallback. Scripts enable hot reload, watch mode, production start, and build tasks.

When to use it

  • Bootstrapping a React SPA with fast local reload and live updates
  • Developing a REST or GraphQL API using Bun and Hono with auto-reload
  • Building a combined frontend + backend local server for full-stack development
  • Serving static files with live reload during frontend development
  • Testing HTTPS-only browser APIs locally with self-signed certs

Best practices

  • Use bun run --hot for fastest dev feedback and bun run --watch for lightweight watching
  • Limit file watching to the src directory to reduce CPU usage
  • Expose an HMR WebSocket endpoint and keep a small client script to reload or refresh modules
  • Keep environment variables in .env.development/.env.production and read process.env in server.ts
  • Add CORS middleware when proxying or calling APIs from a different origin

Example use cases

  • React dev server: serve public/index.html, static assets, and notify clients via /_hmr WebSocket when .tsx files change
  • API dev server: start a Hono app with /api routes, logger and CORS middleware, and auto-reload on code changes
  • Full-stack: mount API under /api and serve frontend from public with SPA fallback to index.html
  • Local HTTPS: run Bun.serve with TLS cert/key for testing Service Worker or WebRTC flows
  • Proxying: forward /api/* to an existing backend during frontend development

FAQ

How do I enable HMR?

Run the dev script with bun run --hot, ensure the client connects to ws://localhost:PORT/_hmr, and confirm the server watches src for changes.

Why is my WebSocket not connecting?

Check the port, firewall, correct ws URL, and that the server was started with the --hot flag or the Bun.serve websocket handler is active.