Skip to main content

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:

  1. The client sends a request with the Upgrade: websocket header
  2. 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 ws library
  • 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 established
  • message(ws: ServerWebSocket, message: string | Buffer): Called when a message is received
  • close(ws: ServerWebSocket, code: number, reason: string): Called when connection closes
  • drain(ws: ServerWebSocket): Called when the socket is ready to receive more data (backpressure)

How WebSocket Upgrade Works

When tapOptions.bun.websocket is configured:

  1. The framework passes the websocket configuration to Bun.serve()
  2. On incoming requests with Upgrade: websocket header, the server attempts upgrade
  3. If upgrade succeeds, your handlers are called at appropriate lifecycle events
  4. 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 established
  • message(ws: WebSocket, message: string): Called when a message is received
  • close(ws: WebSocket): Called when connection closes

How WebSocket Upgrade Works

When tapOptions.deno.websocket is configured:

  1. On incoming requests with Upgrade: websocket header, Deno.upgradeWebSocket() is called
  2. Event handlers (onopen, onmessage, onclose) are attached to the WebSocket
  3. The upgrade response is returned to the client
  4. Your handlers are called at appropriate lifecycle events
  5. If no websocket config is provided, requests are handled normally (no upgrade attempted)

Comparison Table

FeatureNode.jsBunDeno
Libraryws (external)NativeNative
ConfigurationDirect APItapOptions.bun.websockettapOptions.deno.websocket
Auto UpgradeManual setupAutomatic when configuredAutomatic when configured
Path ControlFull controlAll upgrade requestsAll upgrade requests
Typews.WebSocketServerWebSocketWebSocket

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 ws library for maximum control and ecosystem compatibility
  • Bun: Use native websocket with excellent performance and backpressure handling
  • Deno: Use native websocket with Web standard APIs