WebSockets
bun-router provides first-class support for WebSockets, leveraging Bun's high-performance WebSocket implementation. This page covers how to work with WebSockets in your bun-router
applications.
Basic WebSocket Setup
To enable WebSocket support, use the websocket
method on your router:
import type { ServerWebSocket } from 'bun'
import { Router } from 'bun-router'
// Define the type for your WebSocket client data
interface ClientData {
userId: string
room: string
}
const router = new Router<ClientData>()
// Set up HTTP routes
router.get('/', () => new Response('WebSocket Server'))
// Configure WebSocket handling
router.websocket({
// Called when a client connects
open(ws) {
console.log('Client connected:', ws.remoteAddress)
// Set custom data on the WebSocket
ws.data = { userId: generateUserId(), room: 'lobby' }
// Subscribe to a topic (for pub/sub)
ws.subscribe('lobby')
// Send a welcome message
ws.send('Welcome to the server!')
},
// Called when a message is received
message(ws, message) {
const content = typeof message === 'string'
? message
: 'Binary data received'
console.log(`Message from ${ws.data.userId}: ${content}`)
// Echo the message back
ws.send(`Echo: ${content}`)
},
// Called when a client disconnects
close(ws, code, reason) {
console.log(`Client ${ws.data.userId} disconnected: ${reason} (${code})`)
ws.unsubscribe(ws.data.room)
},
// Called when a socket error occurs
error(ws, error) {
console.error(`Error for client ${ws.data.userId}:`, error)
},
// Called when backpressure is relieved
drain(ws) {
console.log(`Backpressure relieved for ${ws.data.userId}`)
}
})
// Start the server
router.serve({ port: 3000 })
WebSocket Configuration Options
You can customize the WebSocket behavior with additional options:
router.websocket({
// Event handlers
open: openHandler,
message: messageHandler,
close: closeHandler,
error: errorHandler,
drain: drainHandler,
// Maximum message size (default: 16MB)
maxPayloadLength: 16 * 1024 * 1024,
// Connection timeout in seconds (default: 120)
idleTimeout: 120,
// Backpressure handling
backpressureLimit: 1024 * 1024, // 1MB
closeOnBackpressureLimit: false,
// Compression settings
perMessageDeflate: {
compress: '16KB', // Use 16KB compression level
decompress: true
},
// Keep-alive settings
sendPings: true,
// Control whether published messages are sent to the publisher
publishToSelf: false
})
Client Data
You can associate custom data with each WebSocket connection by setting ws.data
:
router.websocket<UserSession>({
open(ws) {
ws.data = {
userId: 'user_123',
username: 'john',
authenticated: true,
joinedAt: Date.now()
}
},
message(ws, message) {
// Access the data in any handler
console.log(`Message from ${ws.data.username}`)
}
})
Pub/Sub Messaging
bun-router includes a pub/sub system for WebSockets, allowing you to broadcast messages to groups of clients:
router.websocket({
open(ws) {
// Subscribe to channels
ws.subscribe('announcements')
ws.subscribe(`user:${ws.data.userId}`)
},
message(ws, message) {
if (typeof message !== 'string')
return
// Parse commands
if (message.startsWith('/join ')) {
const room = message.slice(6).trim()
// Leave old room
ws.unsubscribe(ws.data.room)
// Join new room
ws.data.room = room
ws.subscribe(room)
ws.send(`Joined room: ${room}`)
// Announce to the room
router.publish(room, `${ws.data.username} joined the room`)
}
else if (message.startsWith('/whisper ')) {
// Private message
const parts = message.slice(9).split(' ')
const targetUser = parts[0]
const content = parts.slice(1).join(' ')
router.publish(`user:${targetUser}`, `[PM from ${ws.data.username}]: ${content}`)
}
else {
// Regular room message
router.publish(ws.data.room, `${ws.data.username}: ${message}`, true) // Enable compression
}
}
})
// You can also publish from outside the WebSocket handlers
function broadcastAnnouncement(message) {
router.publish('announcements', message)
}
Checking Subscriber Count
You can check how many clients are subscribed to a topic:
router.get('/room-stats', () => {
const lobbyCount = router.subscriberCount('lobby')
const gamingCount = router.subscriberCount('gaming')
return Response.json({
lobby: lobbyCount,
gaming: gamingCount
})
})
Custom HTTP to WebSocket Upgrade
You can manually upgrade an HTTP request to a WebSocket connection:
router.get('/custom-ws', (req) => {
// Check for authentication headers
const token = req.headers.get('Authorization')?.split(' ')[1]
if (!token || !validateToken(token)) {
return new Response('Unauthorized', { status: 401 })
}
// Get user from token
const user = getUserFromToken(token)
// Upgrade the connection
const success = router.upgrade(req, {
// Custom headers for the 101 Switching Protocols response
headers: { 'X-User-ID': user.id },
// Attach user data to the WebSocket
data: {
userId: user.id,
username: user.username,
role: user.role,
room: 'lobby'
}
})
if (!success) {
return new Response('Failed to upgrade connection', { status: 400 })
}
// If upgrade successful, this response is ignored
return new Response('Upgraded to WebSocket')
})
Handling Binary Data
WebSockets can send and receive binary data:
router.websocket({
message(ws, message) {
if (typeof message === 'string') {
// Handle text message
console.log('Text message:', message)
}
else {
// Handle binary message (Uint8Array)
console.log('Binary message, size:', message.length)
// You can send binary data back
const response = new Uint8Array([1, 2, 3, 4])
ws.send(response)
}
}
})
Handling Backpressure
When sending large messages, you may encounter backpressure. The send
method returns a value indicating whether the message was sent, queued, or failed:
router.websocket({
message(ws, message) {
// Generate a large response
const largeData = generateLargeResponse()
// Try to send it
const result = ws.send(largeData)
if (result === -1) {
// Message was queued due to backpressure
console.log('Message queued due to backpressure')
// Store state to resume in drain handler
ws.data.pendingMessages = [...ws.data.pendingMessages || [], getMoreData()]
}
else if (result === 0) {
console.log('Failed to send, connection may be closed')
}
else {
console.log(`Sent ${result} bytes successfully`)
}
},
drain(ws) {
// Socket is ready to receive more data
if (ws.data.pendingMessages?.length) {
const next = ws.data.pendingMessages.shift()
const result = ws.send(next)
if (result !== -1) {
console.log('Sent pending message after drain')
}
}
}
})
Client-Side WebSocket Example
Here's an example of how to connect to your WebSocket server from a browser:
// Connect to the WebSocket server
const ws = new WebSocket('ws://localhost:3000')
// Connection opened
ws.addEventListener('open', (event) => {
console.log('Connected to server')
ws.send('Hello Server!')
})
// Listen for messages
ws.addEventListener('message', (event) => {
console.log('Message from server:', event.data)
})
// Connection closed
ws.addEventListener('close', (event) => {
console.log('Disconnected from server:', event.code, event.reason)
})
// Error handler
ws.addEventListener('error', (event) => {
console.error('WebSocket error:', event)
})
// Send a message when a button is clicked
document.getElementById('send-button').addEventListener('click', () => {
const message = document.getElementById('message-input').value
ws.send(message)
})
// Join a room
function joinRoom(roomName) {
ws.send(`/join ${roomName}`)
}
// Send private message
function sendPrivate(username, message) {
ws.send(`/whisper ${username} ${message}`)
}
Next Steps
Now that you understand WebSockets in bun-router, you might want to explore these related topics:
- WebSocket Patterns - Common patterns for WebSocket applications
- Custom Middleware - Create middleware for WebSocket authentication
- Performance Optimization - Tips for high-performance WebSockets