Bun WebSocket
Bun has a built-in high-performance WebSocket server that supports real-time bidirectional communication. This chapter introduces how to use Bun WebSocket.
WebSocket Server
Basic Server
typescript
const server = Bun.serve({
port: 3000,
fetch(request, server) {
// Upgrade to WebSocket connection
if (server.upgrade(request)) {
return; // Upgrade successful
}
return new Response("Please use WebSocket connection");
},
websocket: {
// Connection opened
open(ws) {
console.log("Client connected");
ws.send("Welcome!");
},
// Message received
message(ws, message) {
console.log("Received message:", message);
ws.send(`You said: ${message}`);
},
// Connection closed
close(ws, code, reason) {
console.log("Connection closed:", code, reason);
},
// Error occurred
error(ws, error) {
console.error("WebSocket error:", error);
},
},
});
console.log(`WebSocket server running at ws://localhost:${server.port}`);Client Connection
javascript
// Browser-side JavaScript
const ws = new WebSocket("ws://localhost:3000");
ws.onopen = () => {
console.log("Connected");
ws.send("Hello, Server!");
};
ws.onmessage = (event) => {
console.log("Received message:", event.data);
};
ws.onclose = () => {
console.log("Connection closed");
};
ws.onerror = (error) => {
console.error("WebSocket error:", error);
};WebSocket Configuration
Complete Configuration
typescript
Bun.serve({
port: 3000,
fetch(request, server) {
// Can pass data during upgrade
const userId = new URL(request.url).searchParams.get("userId");
const success = server.upgrade(request, {
// Data passed to websocket handler
data: {
userId,
connectedAt: Date.now(),
},
});
if (success) return;
return new Response("Upgrade failed", { status: 400 });
},
websocket: {
// Max message size (bytes)
maxPayloadLength: 16 * 1024 * 1024, // 16 MB
// Idle timeout (seconds)
idleTimeout: 120,
// Backpressure limit
backpressureLimit: 1024 * 1024, // 1 MB
// Whether to auto-close idle connections
closeOnBackpressureLimit: false,
// Enable compression
perMessageDeflate: true,
open(ws) {
// Access passed data
console.log("User connected:", ws.data.userId);
},
message(ws, message) {
console.log(`User ${ws.data.userId} says:`, message);
},
close(ws) {
console.log("User disconnected:", ws.data.userId);
},
},
});Message Handling
Sending Messages
typescript
websocket: {
message(ws, message) {
// Send text
ws.send("Text message");
// Send JSON
ws.send(JSON.stringify({ type: "message", data: "Hello" }));
// Send binary data
ws.send(new Uint8Array([1, 2, 3, 4]));
// Check send result
const bytesSent = ws.send("Message");
console.log("Bytes sent:", bytesSent);
},
}Receiving Messages
typescript
websocket: {
message(ws, message) {
// message can be string or Buffer
if (typeof message === "string") {
console.log("Text message:", message);
// Try to parse JSON
try {
const data = JSON.parse(message);
handleJsonMessage(ws, data);
} catch {
handleTextMessage(ws, message);
}
} else {
console.log("Binary message:", message.length, "bytes");
handleBinaryMessage(ws, message);
}
},
}
function handleJsonMessage(ws: ServerWebSocket, data: any) {
switch (data.type) {
case "ping":
ws.send(JSON.stringify({ type: "pong" }));
break;
case "message":
console.log("Received message:", data.content);
break;
}
}Broadcasting and Channels
Pub/Sub Pattern
typescript
const server = Bun.serve({
port: 3000,
fetch(request, server) {
const url = new URL(request.url);
const room = url.searchParams.get("room") || "default";
server.upgrade(request, {
data: { room },
});
},
websocket: {
open(ws) {
// Subscribe to channel
ws.subscribe(ws.data.room);
console.log(`User joined room: ${ws.data.room}`);
// Broadcast to room
server.publish(ws.data.room, `New user joined ${ws.data.room}`);
},
message(ws, message) {
// Send message to all room members
server.publish(ws.data.room, message);
},
close(ws) {
// Unsubscribe (automatically handled)
server.publish(ws.data.room, "User left the room");
},
},
});Subscription Management
typescript
websocket: {
open(ws) {
// Subscribe to multiple channels
ws.subscribe("global");
ws.subscribe(`user:${ws.data.userId}`);
ws.subscribe("notifications");
},
message(ws, message) {
const data = JSON.parse(message as string);
switch (data.action) {
case "subscribe":
ws.subscribe(data.channel);
break;
case "unsubscribe":
ws.unsubscribe(data.channel);
break;
case "publish":
server.publish(data.channel, data.message);
break;
}
},
close(ws) {
// All subscriptions will be automatically cleaned up
},
}Connection Management
Tracking Connections
typescript
// Use Set to track all connections
const connections = new Set<ServerWebSocket>();
Bun.serve({
port: 3000,
fetch(request, server) {
server.upgrade(request);
},
websocket: {
open(ws) {
connections.add(ws);
console.log(`Connections: ${connections.size}`);
},
close(ws) {
connections.delete(ws);
console.log(`Connections: ${connections.size}`);
},
message(ws, message) {
// Broadcast to all connections
for (const client of connections) {
if (client !== ws) {
client.send(message);
}
}
},
},
});
// Send heartbeat periodically
setInterval(() => {
for (const ws of connections) {
ws.ping();
}
}, 30000);Connection Authentication
typescript
Bun.serve({
port: 3000,
async fetch(request, server) {
// Validate token
const url = new URL(request.url);
const token = url.searchParams.get("token");
if (!token) {
return new Response("Missing auth token", { status: 401 });
}
const user = await verifyToken(token);
if (!user) {
return new Response("Invalid auth token", { status: 401 });
}
// Upgrade connection after validation
server.upgrade(request, {
data: { user },
});
},
websocket: {
open(ws) {
console.log(`User ${ws.data.user.name} connected`);
},
message(ws, message) {
// Can access user information
console.log(`${ws.data.user.name}: ${message}`);
},
},
});
async function verifyToken(token: string) {
// Implement token validation logic
if (token === "valid-token") {
return { id: 1, name: "Zhang San" };
}
return null;
}Heartbeat and Keep-alive
Server-side Heartbeat
typescript
websocket: {
open(ws) {
// Send ping
ws.ping();
},
pong(ws) {
console.log("Received pong");
},
message(ws, message) {
// Can also send ping when receiving message
ws.ping();
},
}Client-side Heartbeat
javascript
// Client heartbeat implementation
class WebSocketClient {
constructor(url) {
this.url = url;
this.heartbeatInterval = 30000;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log("Connected");
this.startHeartbeat();
};
this.ws.onclose = () => {
console.log("Connection closed, attempting to reconnect...");
this.stopHeartbeat();
setTimeout(() => this.connect(), 3000);
};
this.ws.onmessage = (event) => {
if (event.data === "pong") {
console.log("Heartbeat OK");
return;
}
// Handle other messages
};
}
startHeartbeat() {
this.heartbeat = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send("ping");
}
}, this.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeat) {
clearInterval(this.heartbeat);
}
}
}Chat Room Example
typescript
// chat-server.ts
interface User {
id: string;
name: string;
}
interface Message {
type: "message" | "join" | "leave" | "users";
user?: string;
content?: string;
users?: string[];
timestamp: number;
}
const users = new Map<ServerWebSocket, User>();
const server = Bun.serve({
port: 3000,
fetch(request, server) {
const url = new URL(request.url);
const name = url.searchParams.get("name");
if (!name) {
return new Response("Please provide a name: ?name=xxx");
}
server.upgrade(request, {
data: {
id: crypto.randomUUID(),
name,
},
});
},
websocket: {
open(ws) {
const user: User = ws.data;
users.set(ws, user);
// Subscribe to chat room
ws.subscribe("chat");
// Broadcast user join
broadcast({
type: "join",
user: user.name,
timestamp: Date.now(),
});
// Send current user list
ws.send(JSON.stringify({
type: "users",
users: Array.from(users.values()).map(u => u.name),
timestamp: Date.now(),
}));
},
message(ws, message) {
const user = users.get(ws);
if (!user) return;
const data = JSON.parse(message as string);
if (data.type === "message") {
broadcast({
type: "message",
user: user.name,
content: data.content,
timestamp: Date.now(),
});
}
},
close(ws) {
const user = users.get(ws);
if (user) {
users.delete(ws);
broadcast({
type: "leave",
user: user.name,
timestamp: Date.now(),
});
}
},
},
});
function broadcast(message: Message) {
server.publish("chat", JSON.stringify(message));
}
console.log(`Chat server running at ws://localhost:${server.port}`);Chat Client
html
<!DOCTYPE html>
<html>
<head>
<title>Bun Chat Room</title>
<style>
#messages { height: 300px; overflow-y: auto; border: 1px solid #ccc; padding: 10px; }
.message { margin: 5px 0; }
.join { color: green; }
.leave { color: red; }
</style>
</head>
<body>
<div id="messages"></div>
<input type="text" id="input" placeholder="Enter message...">
<button onclick="sendMessage()">Send</button>
<script>
const name = prompt("Please enter your name:");
const ws = new WebSocket(`ws://localhost:3000?name=${encodeURIComponent(name)}`);
const messages = document.getElementById("messages");
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
const div = document.createElement("div");
div.className = "message";
switch (data.type) {
case "message":
div.textContent = `${data.user}: ${data.content}`;
break;
case "join":
div.className += " join";
div.textContent = `${data.user} joined the chat room`;
break;
case "leave":
div.className += " leave";
div.textContent = `${data.user} left the chat room`;
break;
case "users":
div.textContent = `Online users: ${data.users.join(", ")}`;
break;
}
messages.appendChild(div);
messages.scrollTop = messages.scrollHeight;
};
function sendMessage() {
const input = document.getElementById("input");
if (input.value) {
ws.send(JSON.stringify({ type: "message", content: input.value }));
input.value = "";
}
}
document.getElementById("input").addEventListener("keypress", (e) => {
if (e.key === "Enter") sendMessage();
});
</script>
</body>
</html>WebSocket Client
Bun as Client
typescript
// Bun can also act as a WebSocket client
const ws = new WebSocket("ws://localhost:3000");
ws.addEventListener("open", () => {
console.log("Connected to server");
ws.send("Hello, Server!");
});
ws.addEventListener("message", (event) => {
console.log("Received:", event.data);
});
ws.addEventListener("close", () => {
console.log("Connection closed");
});
// Keep process running
await Bun.sleep(Infinity);Summary
This chapter introduced:
- ✅ Creating WebSocket servers
- ✅ Sending and receiving messages
- ✅ Pub/Sub broadcast pattern
- ✅ Connection management and authentication
- ✅ Heartbeat and keep-alive mechanisms
- ✅ Complete chat room example
Next Steps
Continue reading Fetch API to learn about Bun's network request features.