WebSockets
WebSockets provide a full-duplex communication channel over a single TCP connection, enabling real-time bidirectional communication between clients and servers. Balda.js supports WebSockets across all three runtimes (Node.js, Bun, and Deno) with runtime-specific configurations.
Overview
WebSocket support in Balda.js is:
- Runtime-specific: Each runtime (Node.js, Bun, Deno) has its own implementation
- Opt-in: WebSocket upgrade only happens when explicitly configured
- Flexible: Configure handlers for connection lifecycle events (open, message, close)
When WebSocket Upgrade Happens
WebSocket upgrade is triggered when both conditions are met:
- The client sends a request with the
Upgrade: websocketheader - WebSocket configuration is provided in the server's
tapOptions
If WebSocket configuration is not provided, the server will handle all requests normally without attempting any WebSocket upgrades.
Runtime-Specific Implementations
Node.js
For Node.js, Balda.js doesn't provide built-in WebSocket handling. Instead, you can use the popular ws library to create a WebSocket server that attaches to the underlying HTTP server.
Installation
npm install ws
Usage
import { Server } from "balda";
import { WebSocketServer } from "ws";
const server = new Server({
port: 8080,
host: "0.0.0.0",
nodeHttpClient: "http",
});
// Get the underlying Node.js server
const nodeServer = server.getNodeServer();
// Create WebSocket server
const wss = new WebSocketServer({
server: nodeServer,
path: "/ws",
});
wss.on("connection", (ws) => {
console.log("New WebSocket connection");
ws.send("Hello from server");
ws.on("message", (data) => {
console.log("Received:", data.toString());
ws.send(`Echo: ${data}`);
});
ws.on("close", () => {
console.log("Connection closed");
});
});
server.listen(({ url }) => {
console.log(`Server listening on ${url}`);
});
Key Points
- Uses the standard
wslibrary - Requires getting the underlying Node server with
server.getNodeServer() - Full control over WebSocket path and configuration
Bun
Bun has native WebSocket support built into Bun.serve(). Balda.js exposes this through the tapOptions.bun.websocket configuration.
Usage
import { Server } from "balda";
const server = new Server({
port: 8080,
host: "0.0.0.0",
tapOptions: {
bun: {
websocket: {
open(ws) {
console.log("WebSocket opened");
ws.send("Welcome to Bun WebSocket!");
},
message(ws, message) {
console.log("Received:", message);
ws.send(`Echo: ${message}`);
},
close(ws, code, reason) {
console.log(`Connection closed: ${code} - ${reason}`);
},
drain(ws) {
console.log("Socket is ready to receive more data");
},
},
},
},
});
server.listen(({ url }) => {
console.log(`Server listening on ${url}`);
});
WebSocket Handler Methods
open(ws: ServerWebSocket): Called when a WebSocket connection is establishedmessage(ws: ServerWebSocket, message: string | Buffer): Called when a message is receivedclose(ws: ServerWebSocket, code: number, reason: string): Called when connection closesdrain(ws: ServerWebSocket): Called when the socket is ready to receive more data (backpressure)
How WebSocket Upgrade Works
When tapOptions.bun.websocket is configured:
- The framework passes the websocket configuration to
Bun.serve() - On incoming requests with
Upgrade: websocketheader, the server attempts upgrade - If upgrade succeeds, your handlers are called at appropriate lifecycle events
- If no websocket config is provided, requests are handled normally (no upgrade attempted)
Deno
Deno also has native WebSocket support. Balda.js exposes this through the tapOptions.deno.websocket configuration.
Usage
import { Server } from "balda";
const server = new Server({
port: 8080,
host: "0.0.0.0",
tapOptions: {
deno: {
websocket: {
open(ws) {
console.log("WebSocket opened");
ws.send("Welcome to Deno WebSocket!");
},
message(ws, message) {
console.log("Received:", message);
ws.send(`Echo: ${message}`);
},
close(ws) {
console.log("Connection closed");
},
},
},
},
});
server.listen(({ url }) => {
console.log(`Server listening on ${url}`);
});
WebSocket Handler Methods
open(ws: WebSocket): Called when a WebSocket connection is establishedmessage(ws: WebSocket, message: string): Called when a message is receivedclose(ws: WebSocket): Called when connection closes
How WebSocket Upgrade Works
When tapOptions.deno.websocket is configured:
- On incoming requests with
Upgrade: websocketheader,Deno.upgradeWebSocket()is called - Event handlers (onopen, onmessage, onclose) are attached to the WebSocket
- The upgrade response is returned to the client
- Your handlers are called at appropriate lifecycle events
- If no websocket config is provided, requests are handled normally (no upgrade attempted)
Comparison Table
| Feature | Node.js | Bun | Deno |
|---|---|---|---|
| Library | ws (external) | Native | Native |
| Configuration | Direct API | tapOptions.bun.websocket | tapOptions.deno.websocket |
| Auto Upgrade | Manual setup | Automatic when configured | Automatic when configured |
| Path Control | Full control | All upgrade requests | All upgrade requests |
| Type | ws.WebSocket | ServerWebSocket | WebSocket |
Security Considerations
1. Validate Origin
// Node.js
wss.on("connection", (ws, req) => {
const origin = req.headers.origin;
if (!isAllowedOrigin(origin)) {
ws.close(1008, "Origin not allowed");
return;
}
});
2. Authenticate Connections
// Use query parameters or headers for authentication
wss.on("connection", (ws, req) => {
const url = new URL(req.url!, `http://${req.headers.host}`);
const token = url.searchParams.get("token");
if (!isValidToken(token)) {
ws.close(1008, "Unauthorized");
return;
}
});
3. Input Sanitization
message(ws, message) {
// Sanitize and validate all input
const sanitized = sanitizeInput(message);
const validated = validateMessage(sanitized);
if (!validated) {
ws.send(JSON.stringify({ error: "Invalid input" }));
return;
}
processMessage(validated);
}
Summary
WebSocket support in Balda.js is:
- Runtime-aware: Different implementations for Node.js, Bun, and Deno
- Opt-in: Only enabled when explicitly configured
- Safe by default: No upgrade attempts without configuration
- Flexible: Full control over connection lifecycle
Choose the approach that best fits your runtime:
- Node.js: Use
wslibrary for maximum control and ecosystem compatibility - Bun: Use native websocket with excellent performance and backpressure handling
- Deno: Use native websocket with Web standard APIs