# Bun Server Patterns HTTP, WebSocket, and streaming patterns with Bun.serve. ## Basic HTTP Server ```typescript Bun.serve({ port: 3000, hostname: '0.0.0.0', // Listen on all interfaces fetch(req) { const url = new URL(req.url); switch (url.pathname) { case '/': return new Response('Hello, World!'); case '/json': return Response.json({ ok: true }); case '/html': return new Response('

Hello

', { headers: { 'Content-Type': 'text/html' } }); default: return new Response('Not Found', { status: 404 }); } }, error(err) { console.error('Server error:', err); return new Response(`Error: ${err.message}`, { status: 500 }); } }); ``` ## Request Handling ```typescript Bun.serve({ async fetch(req) { const url = new URL(req.url); // Method routing if (req.method === 'POST' && url.pathname === '/users') { const body = await req.json(); // Process body... return Response.json({ id: '123', ...body }, { status: 201 }); } // Query parameters if (url.pathname === '/search') { const query = url.searchParams.get('q'); const page = parseInt(url.searchParams.get('page') || '1'); // Search logic... } // Headers const auth = req.headers.get('Authorization'); const contentType = req.headers.get('Content-Type'); // URL parameters (manual parsing) const match = url.pathname.match(/^\/users\/([^/]+)$/); if (match) { const userId = match[1]; // Fetch user... } return new Response('Not Found', { status: 404 }); } }); ``` ## Response Patterns ```typescript // Plain text new Response('Hello') // JSON Response.json({ data: 'value' }) // With status new Response('Created', { status: 201 }) Response.json({ error: 'Not found' }, { status: 404 }) // With headers new Response('data', { headers: { 'Content-Type': 'text/plain', 'Cache-Control': 'max-age=3600', 'X-Custom-Header': 'value' } }) // Redirect Response.redirect('/new-location', 302) // Stream new Response(readableStream, { headers: { 'Content-Type': 'application/octet-stream' } }) ``` ## File Serving ```typescript Bun.serve({ async fetch(req) { const url = new URL(req.url); // Serve static files if (url.pathname.startsWith('/static/')) { const filepath = `./public${url.pathname}`; const file = Bun.file(filepath); if (!(await file.exists())) { return new Response('Not Found', { status: 404 }); } return new Response(file.stream(), { headers: { 'Content-Type': file.type, 'Content-Length': file.size.toString(), 'Cache-Control': 'public, max-age=31536000' } }); } // File download if (url.pathname.startsWith('/download/')) { const filename = url.pathname.split('/').pop(); const file = Bun.file(`./files/${filename}`); return new Response(file.stream(), { headers: { 'Content-Type': 'application/octet-stream', 'Content-Disposition': `attachment; filename="${filename}"` } }); } } }); ``` ## WebSocket Server ```typescript type WebSocketData = { id: string; userId: string; joinedAt: Date; }; const clients = new Map>(); Bun.serve({ port: 3000, fetch(req, server) { const url = new URL(req.url); if (url.pathname === '/ws') { const userId = url.searchParams.get('userId'); if (!userId) { return new Response('userId required', { status: 400 }); } const success = server.upgrade(req, { data: { id: crypto.randomUUID(), userId, joinedAt: new Date() } }); return success ? undefined : new Response('Upgrade failed', { status: 500 }); } return new Response('Hello'); }, websocket: { open(ws) { clients.set(ws.data.id, ws); ws.subscribe('broadcast'); ws.send(JSON.stringify({ type: 'connected', id: ws.data.id })); }, message(ws, message) { const data = JSON.parse(message.toString()); switch (data.type) { case 'broadcast': ws.publish('broadcast', JSON.stringify({ from: ws.data.userId, message: data.message })); break; case 'direct': const target = clients.get(data.targetId); target?.send(JSON.stringify({ from: ws.data.userId, message: data.message })); break; } }, close(ws) { clients.delete(ws.data.id); ws.unsubscribe('broadcast'); } } }); ``` ## Streaming Responses ```typescript // Server-Sent Events Bun.serve({ fetch(req) { const url = new URL(req.url); if (url.pathname === '/events') { const stream = new ReadableStream({ start(controller) { const encoder = new TextEncoder(); const interval = setInterval(() => { const event = `data: ${JSON.stringify({ time: Date.now() })}\n\n`; controller.enqueue(encoder.encode(event)); }, 1000); // Cleanup on close req.signal.addEventListener('abort', () => { clearInterval(interval); controller.close(); }); } }); return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' } }); } } }); // Chunked transfer async function* generateChunks() { for (let i = 0; i < 10; i++) { yield `Chunk ${i}\n`; await Bun.sleep(100); } } const response = new Response( new ReadableStream({ async start(controller) { const encoder = new TextEncoder(); for await (const chunk of generateChunks()) { controller.enqueue(encoder.encode(chunk)); } controller.close(); } }) ); ``` ## Middleware Pattern ```typescript type Handler = (req: Request) => Response | Promise; type Middleware = (req: Request, next: Handler) => Response | Promise; function compose(...middlewares: Middleware[]): Handler { return (req) => { let index = 0; const next: Handler = (req) => { if (index >= middlewares.length) { return new Response('Not Found', { status: 404 }); } const middleware = middlewares[index++]; return middleware(req, next); }; return next(req); }; } // Logging middleware const logging: Middleware = async (req, next) => { const start = Bun.nanoseconds(); const response = await next(req); const duration = (Bun.nanoseconds() - start) / 1_000_000; console.log(`${req.method} ${new URL(req.url).pathname} - ${duration.toFixed(2)}ms`); return response; }; // Auth middleware const auth: Middleware = async (req, next) => { const token = req.headers.get('Authorization')?.replace('Bearer ', ''); if (!token) { return Response.json({ error: 'Unauthorized' }, { status: 401 }); } // Validate token... return next(req); }; // CORS middleware const cors: Middleware = async (req, next) => { if (req.method === 'OPTIONS') { return new Response(null, { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE', 'Access-Control-Allow-Headers': 'Content-Type, Authorization' } }); } const response = await next(req); response.headers.set('Access-Control-Allow-Origin', '*'); return response; }; const handler = compose(cors, logging, auth); Bun.serve({ fetch: handler }); ``` ## Graceful Shutdown ```typescript const server = Bun.serve({ port: 3000, fetch(req) { return new Response('Hello'); } }); process.on('SIGTERM', () => { console.log('Shutting down...'); server.stop(); process.exit(0); }); process.on('SIGINT', () => { console.log('Interrupted, shutting down...'); server.stop(); process.exit(0); }); ``` ## Compression ```typescript import { gzipSync, gunzipSync, deflateSync, inflateSync } from 'bun'; // Gzip compression const data = 'Large data string...'.repeat(1000); const compressed = gzipSync(data); const decompressed = gunzipSync(compressed); // Deflate const deflated = deflateSync('data'); const inflated = inflateSync(deflated); // Gzip HTTP response app.get('/large-data', (c) => { const data = generateLargeDataset(); const json = JSON.stringify(data); const acceptEncoding = c.req.header('accept-encoding') || ''; if (acceptEncoding.includes('gzip')) { return c.body(gzipSync(json), { headers: { 'Content-Type': 'application/json', 'Content-Encoding': 'gzip' } }); } return c.json(data); }); ``` ## TLS/HTTPS ```typescript Bun.serve({ port: 443, tls: { key: Bun.file('./key.pem'), cert: Bun.file('./cert.pem'), }, fetch(req) { return new Response('Secure!'); } }); ```