Bun HTTP Server
Bun has a built-in high-performance HTTP server that allows creating web services with a concise API. This chapter introduces Bun HTTP server usage.
Basic Server
Minimal Example
typescript
const server = Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello, Bun!");
},
});
console.log(`Server running at http://localhost:${server.port}`);Server Configuration
typescript
const server = Bun.serve({
// Port number
port: 3000,
// Hostname
hostname: "127.0.0.1",
// Request handler
fetch(request, server) {
return new Response("Hello!");
},
// Error handler
error(error) {
console.error("Server error:", error);
return new Response("Server error", { status: 500 });
},
});Request Handling
Request Object
typescript
Bun.serve({
port: 3000,
fetch(request) {
// Request method
console.log("Method:", request.method);
// Request URL
const url = new URL(request.url);
console.log("Path:", url.pathname);
console.log("Query params:", url.searchParams);
// Request headers
console.log("Content-Type:", request.headers.get("content-type"));
console.log("User-Agent:", request.headers.get("user-agent"));
return new Response("OK");
},
});Getting Request Body
typescript
Bun.serve({
port: 3000,
async fetch(request) {
// JSON body
if (request.headers.get("content-type")?.includes("application/json")) {
const body = await request.json();
console.log("JSON:", body);
}
// Text body
const text = await request.text();
// FormData
const formData = await request.formData();
// ArrayBuffer
const buffer = await request.arrayBuffer();
return new Response("Request received");
},
});Form Data
typescript
Bun.serve({
port: 3000,
async fetch(request) {
if (request.method === "POST") {
const formData = await request.formData();
// Get fields
const name = formData.get("name");
const email = formData.get("email");
// Get file
const file = formData.get("avatar") as File;
if (file) {
console.log("File name:", file.name);
console.log("File size:", file.size);
// Save file
await Bun.write(`./uploads/${file.name}`, file);
}
return Response.json({ name, email });
}
return new Response("Please use POST request");
},
});Response Handling
Response Object
typescript
Bun.serve({
port: 3000,
fetch(request) {
// Text response
return new Response("Hello, World!");
// With status code
return new Response("Not found", { status: 404 });
// With headers
return new Response("Hello", {
status: 200,
headers: {
"Content-Type": "text/plain; charset=utf-8",
"X-Custom-Header": "custom-value",
},
});
},
});JSON Response
typescript
Bun.serve({
port: 3000,
fetch(request) {
const data = {
message: "Success",
timestamp: new Date().toISOString(),
data: { id: 1, name: "Bun" },
};
// Use Response.json()
return Response.json(data);
// With status code
return Response.json({ error: "Not found" }, { status: 404 });
// Manual creation
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
},
});HTML Response
typescript
Bun.serve({
port: 3000,
fetch(request) {
const html = `
<!DOCTYPE html>
<html>
<head><title>Bun Server</title></head>
<body>
<h1>Welcome to Bun!</h1>
<p>Current time: ${new Date().toLocaleString()}</p>
</body>
</html>
`;
return new Response(html, {
headers: { "Content-Type": "text/html; charset=utf-8" },
});
},
});File Response
typescript
Bun.serve({
port: 3000,
async fetch(request) {
const url = new URL(request.url);
// Return static file
if (url.pathname === "/image.png") {
const file = Bun.file("./public/image.png");
return new Response(file);
}
// Set download filename
if (url.pathname === "/download") {
const file = Bun.file("./files/document.pdf");
return new Response(file, {
headers: {
"Content-Disposition": 'attachment; filename="document.pdf"',
},
});
}
return new Response("Not Found", { status: 404 });
},
});Routing
Simple Routing
typescript
Bun.serve({
port: 3000,
fetch(request) {
const url = new URL(request.url);
const path = url.pathname;
const method = request.method;
// Home page
if (path === "/" && method === "GET") {
return new Response("Home");
}
// API routes
if (path === "/api/users" && method === "GET") {
return Response.json([{ id: 1, name: "John" }]);
}
if (path === "/api/users" && method === "POST") {
return Response.json({ message: "User created" }, { status: 201 });
}
// 404
return new Response("Not Found", { status: 404 });
},
});Route Parameters
typescript
Bun.serve({
port: 3000,
fetch(request) {
const url = new URL(request.url);
const path = url.pathname;
// Match /api/users/:id
const userMatch = path.match(/^\/api\/users\/(\d+)$/);
if (userMatch) {
const userId = userMatch[1];
return Response.json({ id: userId, name: `User ${userId}` });
}
// Match /api/posts/:id/comments/:commentId
const commentMatch = path.match(/^\/api\/posts\/(\d+)\/comments\/(\d+)$/);
if (commentMatch) {
const [, postId, commentId] = commentMatch;
return Response.json({ postId, commentId });
}
return new Response("Not Found", { status: 404 });
},
});Router Class
typescript
// router.ts
type Handler = (request: Request, params: Record<string, string>) => Response | Promise<Response>;
interface Route {
method: string;
pattern: RegExp;
paramNames: string[];
handler: Handler;
}
class Router {
private routes: Route[] = [];
private pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {
const paramNames: string[] = [];
const pattern = path.replace(/:(\w+)/g, (_, name) => {
paramNames.push(name);
return "([^/]+)";
});
return { pattern: new RegExp(`^${pattern}$`), paramNames };
}
add(method: string, path: string, handler: Handler) {
const { pattern, paramNames } = this.pathToRegex(path);
this.routes.push({ method, pattern, paramNames, handler });
}
get(path: string, handler: Handler) { this.add("GET", path, handler); }
post(path: string, handler: Handler) { this.add("POST", path, handler); }
put(path: string, handler: Handler) { this.add("PUT", path, handler); }
delete(path: string, handler: Handler) { this.add("DELETE", path, handler); }
async handle(request: Request): Promise<Response> {
const url = new URL(request.url);
for (const route of this.routes) {
if (route.method !== request.method) continue;
const match = url.pathname.match(route.pattern);
if (match) {
const params: Record<string, string> = {};
route.paramNames.forEach((name, i) => {
params[name] = match[i + 1];
});
return route.handler(request, params);
}
}
return new Response("Not Found", { status: 404 });
}
}
// Usage
const router = new Router();
router.get("/", () => new Response("Home"));
router.get("/api/users", () => Response.json([{ id: 1 }]));
router.get("/api/users/:id", (req, params) =>
Response.json({ id: params.id })
);
router.post("/api/users", async (req) => {
const body = await req.json();
return Response.json({ created: body }, { status: 201 });
});
Bun.serve({
port: 3000,
fetch: (req) => router.handle(req),
});Middleware Pattern
Implementing Middleware
typescript
type Middleware = (
request: Request,
next: () => Promise<Response>
) => Promise<Response>;
function compose(middlewares: Middleware[]) {
return async (request: Request): Promise<Response> => {
let index = 0;
async function next(): Promise<Response> {
if (index >= middlewares.length) {
return new Response("Not Found", { status: 404 });
}
const middleware = middlewares[index++];
return middleware(request, next);
}
return next();
};
}
// Logger middleware
const logger: Middleware = async (req, next) => {
const start = Date.now();
const response = await next();
console.log(`${req.method} ${new URL(req.url).pathname} - ${Date.now() - start}ms`);
return response;
};
// CORS middleware
const cors: Middleware = async (req, next) => {
const response = await next();
response.headers.set("Access-Control-Allow-Origin", "*");
response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
return response;
};
// Handler middleware
const handler: Middleware = async (req, next) => {
return new Response("Hello!");
};
// Compose and use
const app = compose([logger, cors, handler]);
Bun.serve({
port: 3000,
fetch: app,
});Static File Serving
Simple Static Server
typescript
import path from "node:path";
const PUBLIC_DIR = "./public";
Bun.serve({
port: 3000,
async fetch(request) {
const url = new URL(request.url);
let filePath = path.join(PUBLIC_DIR, url.pathname);
// Directory default returns index.html
if (filePath.endsWith("/")) {
filePath += "index.html";
}
const file = Bun.file(filePath);
if (await file.exists()) {
return new Response(file);
}
return new Response("Not Found", { status: 404 });
},
});MIME Type Handling
typescript
const MIME_TYPES: Record<string, string> = {
".html": "text/html",
".css": "text/css",
".js": "application/javascript",
".json": "application/json",
".png": "image/png",
".jpg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".woff": "font/woff",
".woff2": "font/woff2",
};
function getMimeType(filePath: string): string {
const ext = path.extname(filePath).toLowerCase();
return MIME_TYPES[ext] || "application/octet-stream";
}
Bun.serve({
port: 3000,
async fetch(request) {
const url = new URL(request.url);
const filePath = path.join("./public", url.pathname);
const file = Bun.file(filePath);
if (await file.exists()) {
return new Response(file, {
headers: { "Content-Type": getMimeType(filePath) },
});
}
return new Response("Not Found", { status: 404 });
},
});HTTPS Server
TLS Configuration
typescript
Bun.serve({
port: 443,
// TLS certificates
tls: {
key: Bun.file("./certs/key.pem"),
cert: Bun.file("./certs/cert.pem"),
// Optional: CA certificate
// ca: Bun.file("./certs/ca.pem"),
// Optional: passphrase
// passphrase: "your-passphrase",
},
fetch(request) {
return new Response("Secure connection!");
},
});Self-signed Certificate
bash
# Generate self-signed certificate (for development)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodesServer Control
Stopping Server
typescript
const server = Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello!");
},
});
// Stop server
server.stop();
// Or stop immediately
server.stop(true);Reload
typescript
// Hot reload support
export default {
port: 3000,
fetch(request: Request) {
return new Response("Hello!");
},
};
// Run: bun --hot server.tsGetting Server Info
typescript
const server = Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello!");
},
});
console.log("Port:", server.port);
console.log("Hostname:", server.hostname);
console.log("Development:", server.development);
console.log("Pending requests:", server.pendingRequests);Complete REST API Example
typescript
// api-server.ts
interface User {
id: number;
name: string;
email: string;
}
const users: User[] = [
{ id: 1, name: "John", email: "john@example.com" },
{ id: 2, name: "Jane", email: "jane@example.com" },
];
let nextId = 3;
const server = Bun.serve({
port: 3000,
async fetch(request) {
const url = new URL(request.url);
const path = url.pathname;
const method = request.method;
// CORS
if (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",
},
});
}
// GET /api/users
if (path === "/api/users" && method === "GET") {
return Response.json(users);
}
// GET /api/users/:id
const getMatch = path.match(/^\/api\/users\/(\d+)$/);
if (getMatch && method === "GET") {
const user = users.find(u => u.id === parseInt(getMatch[1]));
if (user) {
return Response.json(user);
}
return Response.json({ error: "User not found" }, { status: 404 });
}
// POST /api/users
if (path === "/api/users" && method === "POST") {
const body = await request.json();
const newUser: User = {
id: nextId++,
name: body.name,
email: body.email,
};
users.push(newUser);
return Response.json(newUser, { status: 201 });
}
// PUT /api/users/:id
const putMatch = path.match(/^\/api\/users\/(\d+)$/);
if (putMatch && method === "PUT") {
const index = users.findIndex(u => u.id === parseInt(putMatch[1]));
if (index !== -1) {
const body = await request.json();
users[index] = { ...users[index], ...body };
return Response.json(users[index]);
}
return Response.json({ error: "User not found" }, { status: 404 });
}
// DELETE /api/users/:id
const deleteMatch = path.match(/^\/api\/users\/(\d+)$/);
if (deleteMatch && method === "DELETE") {
const index = users.findIndex(u => u.id === parseInt(deleteMatch[1]));
if (index !== -1) {
users.splice(index, 1);
return new Response(null, { status: 204 });
}
return Response.json({ error: "User not found" }, { status: 404 });
}
return Response.json({ error: "Not found" }, { status: 404 });
},
error(error) {
console.error(error);
return Response.json({ error: "Server error" }, { status: 500 });
},
});
console.log(`API server running at http://localhost:${server.port}`);Summary
This chapter covered:
- ✅ Creating basic HTTP server
- ✅ Handling requests and responses
- ✅ Routing and parameter parsing
- ✅ Middleware pattern
- ✅ Static file serving
- ✅ HTTPS configuration
- ✅ Complete REST API example
Next Steps
Continue reading WebSocket to learn about Bun's real-time communication features.