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

bun-dev-server

SKILL.md

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

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

bun add react react-dom
bun add -d @types/react @types/react-dom

For API with Hono (recommended):

bun add hono

For Full-Stack:

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:

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:

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

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

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

Create src/App.tsx:

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:

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:

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:

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

# Server
PORT=3000
NODE_ENV=development

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

# Features
ENABLE_HMR=true

Create .env.production:

# Server
PORT=8080
NODE_ENV=production

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

# Features
ENABLE_HMR=false

Load environment in server.ts:

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

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

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

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:

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

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:

# 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

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

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

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
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
Weekly Installs
24
GitHub Stars
3
First Seen
Jan 26, 2026
Installed on
codex18
cursor18
opencode17
gemini-cli17
github-copilot17
amp16