### Installation with Valibot Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/getting-started.md Installs the bun-ws-router library with Valibot for message validation. Valibot is a smaller alternative to Zod. ```bash bun add bun-ws-router valibot ``` -------------------------------- ### Quick Start WebSocket Server Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/getting-started.md A minimal example demonstrating how to set up a WebSocket server using Bun WebSocket Router. It includes handling connection open, message reception, broadcasting, and connection close events. ```typescript import { WebSocketRouter, messageSchema } from "bun-ws-router"; import { z } from "zod"; // Define a message schema const ChatMessage = messageSchema( "CHAT_MESSAGE", z.object({ text: z.string(), roomId: z.string(), }), ); // Create router and define handlers const router = new WebSocketRouter() .onOpen((ws) => { console.log(`Client ${ws.data.clientId} connected`); }) .onMessage(ChatMessage, (ctx) => { // TypeScript knows ctx.payload has { text: string, roomId: string } console.log(`Message from ${ctx.clientId}: ${ctx.payload.text}`); // Broadcast to room ctx.publish(`room:${ctx.payload.roomId}`, ChatMessage, { text: ctx.payload.text, roomId: ctx.payload.roomId, }); }) .onClose((ws) => { console.log(`Client ${ws.data.clientId} disconnected`); }); // Create Bun server Bun.serve({ port: 3000, fetch(req, server) { if (server.upgrade(req)) { return; // WebSocket upgrade successful } return new Response("Please use a WebSocket client"); }, websocket: router.handlers(), }); console.log("WebSocket server running on ws://localhost:3000"); ``` -------------------------------- ### Installation with Zod Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/getting-started.md Installs the bun-ws-router library along with Zod for message validation. Zod is the default and recommended validation library. ```bash bun add bun-ws-router zod ``` -------------------------------- ### Getting Started with Bun WS Router Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/index.md This example demonstrates the basic setup for using Bun WS Router. It shows how to define message schemas using Zod and create a simple routing handler. ```typescript import { WebSocketServer } from 'bun'; import { createRouter } from 'bun-ws-router'; import { z } from 'zod'; // Define message schemas using Zod const messageSchema = z.object({ type: z.string(), payload: z.any().optional(), }); const pingSchema = z.object({ type: z.literal('ping'), }); const echoSchema = z.object({ type: z.literal('echo'), payload: z.string(), }); // Create the router const router = createRouter([ { schema: pingSchema, handler: async (ws, message) => { ws.send(JSON.stringify({ type: 'pong' })); }, }, { schema: echoSchema, handler: async (ws, message) => { ws.send(JSON.stringify({ type: 'message', payload: message.payload })); }, }, ]); // Create the WebSocket server const server = new WebSocketServer({ port: 3000, async onMessage(ws, message) { try { const parsedMessage = JSON.parse(message.toString()); await router.handle(ws, parsedMessage); } catch (error) { console.error('Failed to process message:', error); ws.send(JSON.stringify({ type: 'error', message: 'Invalid message format' })); } }, async onOpen(ws) { console.log('Client connected:', ws.remoteAddress); }, async onClose(ws, code, reason) { console.log('Client disconnected:', ws.remoteAddress, code, reason); }, }); console.log(`WebSocket server is running on ws://localhost:3000`); ``` -------------------------------- ### Integrating with Hono HTTP Framework Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/getting-started.md Provides an example of integrating Bun WebSocket Router with the Hono web framework. It shows how to set up regular HTTP routes and a WebSocket endpoint for upgrades. ```typescript import { Hono } from "hono"; import { WebSocketRouter } from "bun-ws-router"; const app = new Hono(); const router = new WebSocketRouter(); // Regular HTTP routes app.get("/", (c) => c.text("WebSocket server")); // WebSocket endpoint app.get("/ws", (c) => { const success = c.env.server.upgrade(c.req.raw, { data: { clientId: crypto.randomUUID(), // Add any auth data here }, }); if (success) return; // WebSocket upgrade successful return c.text("WebSocket upgrade failed", 426); }); // Start server Bun.serve({ port: 3000, fetch: app.fetch, websocket: router.handlers(), }); ``` -------------------------------- ### Handling Messages with Handlers Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/getting-started.md Shows how to register message handlers for specific message types using the `onMessage` method of the `WebSocketRouter`. Includes examples for responding to a ping and handling room joins. ```typescript router .onMessage(PingMessage, (ctx) => { // Respond with PONG ctx.send({ type: "PONG" }); }) .onMessage(JoinRoomMessage, (ctx) => { // Join the room ctx.subscribe(`room:${ctx.payload.roomId}`); // Notify others in the room ctx.publish(`room:${ctx.payload.roomId}`, { type: "USER_JOINED", payload: { username: ctx.payload.username }, }); }); ``` -------------------------------- ### Creating Message Schemas with Zod Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/getting-started.md Demonstrates how to define message schemas using the `messageSchema` helper function from `bun-ws-router` and Zod for validation. Supports messages with and without payloads. ```typescript import { messageSchema } from "bun-ws-router"; import { z } from "zod"; // Simple message without payload const PingMessage = messageSchema("PING"); // Message with validated payload const JoinRoomMessage = messageSchema( "JOIN_ROOM", z.object({ roomId: z.uuid(), username: z.string().min(1).max(20), }), ); ``` -------------------------------- ### Install bun-ws-router with Valibot Source: https://github.com/kriasoft/bun-ws-router/blob/main/README.md Installs the bun-ws-router package along with the Valibot validation library and development types for Bun. ```bash bun add bun-ws-router valibot bun add @types/bun -D ``` -------------------------------- ### Install Zod or Valibot Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/valibot-integration.md Commands to install Zod, Valibot, or both for use with the WebSocket router. ```bash # For Zod users bun add zod # For Valibot users bun add valibot # Or both for migration bun add zod valibot ``` -------------------------------- ### Message Structure Definition Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/getting-started.md Illustrates the standard structure for messages exchanged with the WebSocket server. It includes type, metadata (clientId, timestamp), and an optional validated payload. ```typescript { type: string, // Message type for routing meta: { // Metadata (auto-populated) clientId: string, // Unique client identifier timestamp: number, // Unix timestamp }, payload?: any // Your data (validated by schema) } ``` -------------------------------- ### Install bun-ws-router with Zod Source: https://github.com/kriasoft/bun-ws-router/blob/main/README.md Installs the bun-ws-router package along with the Zod validation library and development types for Bun. ```bash bun add bun-ws-router zod bun add @types/bun -D ``` -------------------------------- ### Zod Implementation Example Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/valibot-integration.md Example of using the WebSocketRouter with Zod for defining message schemas and registering handlers. ```typescript import { WebSocketRouter, messageSchema } from "bun-ws-router/zod"; import { z } from "zod"; // Create message schemas const JoinRoomMessage = messageSchema("JOIN_ROOM", { roomId: z.string(), userId: z.string(), }); const LeaveRoomMessage = messageSchema("LEAVE_ROOM", { roomId: z.string(), }); // Create router const router = new WebSocketRouter<{ userId?: string }>(); // Register handlers router.onMessage(JoinRoomMessage, ({ ws, payload, meta, send }) => { console.log(`User ${payload.userId} joining room ${payload.roomId}`); // Type-safe response send(messageSchema("ROOM_JOINED", { success: true }), { success: true, }); }); ``` -------------------------------- ### Connection Opening Handler Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/core-concepts.md Example of handling a new WebSocket connection. It logs the client connection and sends a welcome message with client-specific details. ```typescript router.onOpen((ws) => { // ws.data.clientId is automatically available console.log(`Client ${ws.data.clientId} connected`); // Send welcome message ws.send( JSON.stringify({ type: "WELCOME", meta: { clientId: ws.data.clientId, timestamp: Date.now(), }, payload: { message: "Connected successfully" }, }), ); }); ``` -------------------------------- ### Valibot Implementation Example Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/valibot-integration.md Example of using the WebSocketRouter with Valibot for defining message schemas and registering handlers. ```typescript import { WebSocketRouter, messageSchema } from "bun-ws-router/valibot"; import * as v from "valibot"; // Create message schemas const JoinRoomMessage = messageSchema( "JOIN_ROOM", v.object({ roomId: v.string(), userId: v.string(), }), ); const LeaveRoomMessage = messageSchema( "LEAVE_ROOM", v.object({ roomId: v.string(), }), ); // Create router const router = new WebSocketRouter<{ userId?: string }>(); // Register handlers router.onMessage(JoinRoomMessage, ({ ws, payload, meta, send }) => { console.log(`User ${payload.userId} joining room ${payload.roomId}`); // Type-safe response send(messageSchema("ROOM_JOINED", v.object({ success: v.boolean() })), { success: true, }); }); ``` -------------------------------- ### Basic WebSocket Server Setup with Hono Source: https://github.com/kriasoft/bun-ws-router/blob/main/README.md Demonstrates setting up a Bun server that handles both HTTP requests using Hono and WebSocket connections using bun-ws-router. It includes upgrading requests to WebSocket and adding routes. ```typescript import { Hono } from "hono"; import { WebSocketRouter } from "bun-ws-router"; // Uses Zod by default import { exampleRouter } from "./example"; // HTTP router const app = new Hono(); app.get("/", (c) => c.text("Welcome to Hono!")); // WebSocket router const ws = new WebSocketRouter(); ws.addRoutes(exampleRouter); // Add routes from another file Bun.serve({ port: 3000, fetch(req, server) { const url = new URL(req.url); // Handle WebSocket upgrade requests if (url.pathname === "/ws") { return ws.upgrade(req, { server, }); } // Handle regular HTTP requests return app.fetch(req, { server }); }, // Handle WebSocket connections websocket: ws.websocket, }); console.log(`WebSocket server listening on ws://localhost:3000/ws`); ``` -------------------------------- ### Gradual Migration with Dual Routers Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/valibot-integration.md Provides an example of how to use both Zod and Valibot routers simultaneously during a migration process and merge them. ```typescript // Legacy Zod handlers import { WebSocketRouter as ZodRouter } from "bun-ws-router/zod"; // New Valibot handlers import { WebSocketRouter as ValibotRouter } from "bun-ws-router/valibot"; const zodRouter = new ZodRouter(); const valibotRouter = new ValibotRouter(); // Merge routers zodRouter.addRoutes(valibotRouter); ``` -------------------------------- ### Input Validation with Zod Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/deployment.md Demonstrates strict schema validation for incoming messages using Zod. It includes examples for limiting string lengths, validating email and URL formats, sanitizing HTML content, validating enum types, and limiting array sizes. ```typescript // Strict schema validation const MessageSchema = messageSchema( "MESSAGE", z.object({ // Limit string lengths text: z.string().min(1).max(1000), // Validate formats email: z.email(), url: z.url().startsWith("https://"), // Sanitize HTML content: z.string().transform(sanitizeHtml), // Validate enums type: z.enum(["text", "image", "video"]), // Limit array sizes tags: z.array(z.string()).max(10), }), ); ``` -------------------------------- ### Efficient Broadcasting with Bun Publish Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/deployment.md Optimizes message broadcasting by leveraging Bun's native `publish` method for efficient distribution to subscribed clients. This is used within the `onMessage` handler for `BroadcastMessage`. ```typescript // Efficient broadcast using Bun's publish router.onMessage(BroadcastMessage, (ctx) => { // Use native publish for performance ctx.ws.publish( "global", JSON.stringify({ type: "BROADCAST", payload: ctx.payload, }), ); }); // Subscribe clients efficiently router.onOpen((ws) => { ws.subscribe("global"); ws.subscribe(`user:${ws.data.clientId}`); }); ``` -------------------------------- ### Structured Logging with Pino Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/deployment.md Implements structured logging for WebSocket events using the Pino library. It configures Pino for pretty-printing logs in development and standard JSON logging in production, capturing connection, message, and error events. ```typescript import pino from "pino"; const logger = pino({ level: process.env.LOG_LEVEL || "info", transport: { target: "pino-pretty", options: { colorize: process.env.NODE_ENV !== "production", }, }, }); router .onOpen((ws) => { logger.info({ event: "ws_connect", clientId: ws.data.clientId, ip: ws.data.ip, }); }) .onMessage(AnyMessage, (ctx) => { logger.debug({ event: "ws_message", clientId: ctx.clientId, type: ctx.ws.data.lastMessageType, size: JSON.stringify(ctx.payload).length, }); }) .onError((ws, error) => { logger.error({ event: "ws_error", clientId: ws.data.clientId, error: error.message, stack: error.stack, }); }); ``` -------------------------------- ### Health Check Endpoints Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/deployment.md Implements health check endpoints for monitoring application status. The `/health` endpoint provides general status, while `/health/ready` checks critical dependencies like Redis. ```typescript const healthRouter = new Hono(); healthRouter.get("/health", (c) => { return c.json({ status: "healthy", timestamp: Date.now(), connections: connectionCount, uptime: process.uptime(), }); }); healthRouter.get("/health/ready", async (c) => { try { // Check dependencies await redis.ping(); return c.json({ status: "ready" }); } catch (error) { return c.json({ status: "not ready", error: error.message }, 503); } }); ``` -------------------------------- ### Bun PubSub Broadcasting Example Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/core-concepts.md Demonstrates how to subscribe to a room, publish a message to all subscribers in that room, and unsubscribe using Bun's native PubSub. This is useful for real-time communication features like chat. ```typescript ctx.subscribe(`room:${roomId}`); ctx.publish(`room:${roomId}`, ChatMessage, { text: "Hello everyone!", roomId: roomId, }); ctx.unsubscribe(`room:${roomId}`); ``` -------------------------------- ### Chat Application with Bun WebSocket Router Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/examples.md A complete chat room implementation demonstrating user joining, message sending, and leaving rooms. It utilizes Zod for schema validation and Bun's WebSocket capabilities for real-time communication. ```typescript import { WebSocketRouter, messageSchema, ErrorCode } from "bun-ws-router"; import { z } from "zod"; // Message schemas const JoinRoomMessage = messageSchema( "JOIN_ROOM", z.object({ roomId: z.uuid(), username: z.string().min(1).max(20), }), ); const SendMessageMessage = messageSchema( "SEND_MESSAGE", z.object({ roomId: z.uuid(), text: z.string().min(1).max(500), }), ); const LeaveRoomMessage = messageSchema( "LEAVE_ROOM", z.object({ roomId: z.uuid(), }), ); // Store active users per room const rooms = new Map>(); // Create router const router = new WebSocketRouter<{ username?: string }>() .onOpen((ws) => { console.log(`Client ${ws.data.clientId} connected`); // Send welcome message ws.send( JSON.stringify({ type: "WELCOME", meta: { clientId: ws.data.clientId, timestamp: Date.now(), }, payload: { message: "Connected to chat server", }, }), ); }) .onMessage(JoinRoomMessage, (ctx) => { const { roomId, username } = ctx.payload; // Store username ctx.setData({ username }); // Create room if doesn't exist if (!rooms.has(roomId)) { rooms.set(roomId, new Set()); } // Add user to room rooms.get(roomId)!.add(ctx.clientId); // Subscribe to room updates ctx.subscribe(`room:${roomId}`); // Notify room members ctx.publish(`room:${roomId}`, { type: "USER_JOINED", meta: { clientId: ctx.clientId, timestamp: Date.now(), }, payload: { username, userCount: rooms.get(roomId)!.size, }, }); // Confirm join ctx.send({ type: "JOIN_SUCCESS", payload: { roomId, userCount: rooms.get(roomId)!.size, }, }); }) .onMessage(SendMessageMessage, (ctx) => { const { roomId, text } = ctx.payload; const userData = ctx.getData<{ username?: string }>(); // Check if user is in room if (!rooms.get(roomId)?.has(ctx.clientId)) { ctx.send({ type: "ERROR", payload: { code: ErrorCode.FORBIDDEN, message: "You must join the room first", }, }); return; } // Broadcast message to room ctx.publish(`room:${roomId}`, { type: "MESSAGE", meta: { clientId: ctx.clientId, timestamp: Date.now(), }, payload: { username: userData.username || "Anonymous", text, }, }); }) .onMessage(LeaveRoomMessage, (ctx) => { const { roomId } = ctx.payload; const userData = ctx.getData<{ username?: string }>(); // Remove from room rooms.get(roomId)?.delete(ctx.clientId); // Unsubscribe ctx.unsubscribe(`room:${roomId}`); // Notify others ctx.publish(`room:${roomId}`, { type: "USER_LEFT", payload: { username: userData.username || "Anonymous", userCount: rooms.get(roomId)?.size || 0, }, }); }) .onClose((ws) => { // Clean up user from all rooms for (const [roomId, users] of rooms) { if (users.has(ws.data.clientId)) { users.delete(ws.data.clientId); // Notify room if user was in it ws.publish( `room:${roomId}`, JSON.stringify({ type: "USER_DISCONNECTED", payload: { userCount: users.size, }, }), ); } } }); // Start server Bun.serve({ port: 3000, fetch(req) { if (req.headers.get("upgrade") === "websocket") { return this.upgrade(req) ? undefined : new Response("WebSocket upgrade failed", { status: 426 }); } return new Response("WebSocket server"); }, websocket: router.handlers(), }); ``` -------------------------------- ### Message Compression with Per-Message Deflate Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/deployment.md Enables message compression for WebSocket connections using the `perMessageDeflate` option in Bun. This optimizes network usage by compressing messages larger than a specified threshold. ```typescript // Enable per-message deflate const server = Bun.serve({ websocket: { ...router.handlers(), // Enable compression perMessageDeflate: { threshold: 1024, // Compress messages > 1KB compress: true, }, }, }); ``` -------------------------------- ### Valibot Adapter for Bun WS Router Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/index.md This example shows how to use the Valibot adapter for Bun WS Router, highlighting its smaller bundle size compared to Zod. It defines message schemas using Valibot. ```typescript import { WebSocketServer } from 'bun'; import { createRouter } from 'bun-ws-router/valibot'; // Import Valibot adapter import { object, literal, string } from 'valibot'; // Define message schemas using Valibot const pingSchema = object({ type: literal('ping'), }); const echoSchema = object({ type: literal('echo'), payload: string(), }); // Create the router with Valibot schemas const router = createRouter([ { schema: pingSchema, handler: async (ws, message) => { ws.send(JSON.stringify({ type: 'pong' })); }, }, { schema: echoSchema, handler: async (ws, message) => { ws.send(JSON.stringify({ type: 'message', payload: message.payload })); }, }, ]); // WebSocket server setup (similar to Zod example) const server = new WebSocketServer({ port: 3000, async onMessage(ws, message) { try { const parsedMessage = JSON.parse(message.toString()); await router.handle(ws, parsedMessage); } catch (error) { console.error('Failed to process message:', error); ws.send(JSON.stringify({ type: 'error', message: 'Invalid message format' })); } }, }); console.log(`WebSocket server is running on ws://localhost:3000`); ``` -------------------------------- ### Horizontal Scaling with Redis Pub/Sub Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/deployment.md Enables horizontal scaling of WebSocket applications by using Redis for inter-instance communication via Pub/Sub. It publishes broadcast messages to Redis and subscribes to receive them, forwarding them to local clients. ```typescript import { createClient } from "redis"; const redis = createClient({ url: process.env.REDIS_URL, }); await redis.connect(); // Pub/Sub across instances router.onMessage(BroadcastMessage, async (ctx) => { // Publish to Redis await redis.publish( "broadcast", JSON.stringify({ type: "BROADCAST", payload: ctx.payload, origin: process.env.INSTANCE_ID, }), ); }); // Subscribe to Redis broadcasts redis.subscribe("broadcast", (message) => { const data = JSON.parse(message); // Broadcast to local clients server.publish("global", message); }); ``` -------------------------------- ### Broadcasting Messages Source: https://github.com/kriasoft/bun-ws-router/blob/main/CLAUDE.md Provides examples of broadcasting messages to multiple clients using the `publish` helper function. It supports broadcasting with schema validation for both individual WebSockets and the entire server. ```typescript import { publish } from "bun-ws-router/zod/publish"; // With ServerWebSocket instance: publish(ws, topic, schema, payload, meta?); // With Server instance: publish(server, topic, schema, payload, meta?); ``` -------------------------------- ### Rate Limiter Class Implementation Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/examples.md A custom class to manage request rates for clients. It tracks request timestamps within a defined time window to enforce limits. Dependencies include standard JavaScript Map and Date objects. ```typescript class RateLimiter { private requests = new Map(); constructor( private maxRequests: number, private windowMs: number, ) {} check(clientId: string): boolean { const now = Date.now(); const requests = this.requests.get(clientId) || []; // Remove old requests const validRequests = requests.filter((time) => now - time < this.windowMs); // Check limit if (validRequests.length >= this.maxRequests) { return false; } // Add current request validRequests.push(now); this.requests.set(clientId, validRequests); return true; } reset(clientId: string) { this.requests.delete(clientId); } } ``` -------------------------------- ### Rate Limiting Implementation Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/deployment.md Applies rate limiting to WebSocket connections and messages using `rate-limiter-flexible`. It sets limits for connections per IP and messages per client, and handles responses for exceeding these limits. ```typescript import { RateLimiterMemory } from "rate-limiter-flexible"; // Create rate limiters const messageLimiter = new RateLimiterMemory({ points: config.messageRateLimit, duration: 60, // Per minute }); const connectionLimiter = new RateLimiterMemory({ points: config.maxConnectionsPerIP, duration: 0, // No expiry }); // Apply rate limiting const server = Bun.serve({ async fetch(req) { const ip = req.headers.get("x-forwarded-for") || "unknown"; try { // Check connection limit await connectionLimiter.consume(ip); if (server.upgrade(req)) { return; } } catch { return new Response("Too many connections", { status: 429 }); } }, websocket: { ...router.handlers(), async message(ws, message) { try { // Check message rate limit await messageLimiter.consume(ws.data.clientId); // Process message router.handlers().message(ws, message); } catch { ws.send( JSON.stringify({ type: "ERROR", payload: { code: "RATE_LIMIT", message: "Too many messages", }, }), ); } }, }, }); ``` -------------------------------- ### WebSocket Memory Management Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/deployment.md Manages WebSocket resources by cleaning up intervals and associated data when connections are closed. It uses a Map to store cleanup functions keyed by client ID. ```typescript const cleanupManager = new Map void>(); router .onOpen((ws) => { const clientId = ws.data.clientId; const cleanup: Array<() => void> = []; // Set idle timeout const idleTimer = setInterval(() => { if (Date.now() - ws.data.lastActivity > config.idleTimeout) { ws.close(1000, "Idle timeout"); } }, 60000); cleanup.push(() => clearInterval(idleTimer)); // Store cleanup functions cleanupManager.set(clientId, () => { cleanup.forEach((fn) => fn()); cleanupManager.delete(clientId); }); }) .onMessage(AnyMessage, (ctx) => { // Update activity timestamp ctx.ws.data.lastActivity = Date.now(); }) .onClose((ws) => { // Run cleanup cleanupManager.get(ws.data.clientId)?.(); }); ``` -------------------------------- ### Prometheus Metrics Collection Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/deployment.md Collects WebSocket metrics such as connection counts, message throughput, errors, and message sizes using Prometheus client libraries. It exposes these metrics via a `/metrics` endpoint. ```typescript // Prometheus metrics import { register, Counter, Gauge, Histogram } from "prom-client"; const metrics = { connections: new Gauge({ name: "ws_connections_total", help: "Total WebSocket connections", }), messages: new Counter({ name: "ws_messages_total", help: "Total messages processed", labelNames: ["type"], }), errors: new Counter({ name: "ws_errors_total", help: "Total errors", labelNames: ["code"], }), messageSize: new Histogram({ name: "ws_message_size_bytes", help: "Message size in bytes", buckets: [100, 1000, 10000, 100000], }), }; // Track metrics router .onOpen(() => metrics.connections.inc()) .onClose(() => metrics.connections.dec()) .onMessage(AnyMessage, (ctx) => { const size = JSON.stringify(ctx.payload).length; metrics.messages.inc({ type: ctx.ws.data.lastMessageType }); metrics.messageSize.observe(size); }); // Expose metrics endpoint app.get("/metrics", (c) => c.text(register.metrics())); ``` -------------------------------- ### Environment Configuration Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/deployment.md Defines application configuration using environment variables for production settings. Includes port, WebSocket path, JWT secret, CORS origin, rate limiting parameters, timeouts, and payload size. It also validates the presence of the JWT secret. ```typescript export const config = { port: parseInt(process.env.PORT || "3000"), wsPath: process.env.WS_PATH || "/ws", // Security jwtSecret: process.env.JWT_SECRET!, corsOrigin: process.env.CORS_ORIGIN || "*", // Rate limiting maxConnectionsPerIP: parseInt(process.env.MAX_CONNECTIONS_PER_IP || "10"), messageRateLimit: parseInt(process.env.MESSAGE_RATE_LIMIT || "100"), // Timeouts authTimeout: parseInt(process.env.AUTH_TIMEOUT || "5000"), idleTimeout: parseInt(process.env.IDLE_TIMEOUT || "300000"), // Scaling maxPayloadSize: parseInt(process.env.MAX_PAYLOAD_SIZE || "1048576"), // 1MB }; // Validate required env vars if (!config.jwtSecret) { throw new Error("JWT_SECRET environment variable is required"); } ``` -------------------------------- ### Client-side Message Creation Source: https://github.com/kriasoft/bun-ws-router/blob/main/README.md Provides an example of creating type-safe WebSocket messages on the client-side using the `createMessage` helper function. It includes defining message schemas with Zod and sending validated messages over a WebSocket connection. ```ts import { createMessage, messageSchema } from "bun-ws-router"; import { z } from "zod"; // Define your message schemas (same as server) const JoinRoomMessage = messageSchema("JOIN_ROOM", { roomId: z.string(), }); const SendMessage = messageSchema("SEND_MESSAGE", { roomId: z.string(), text: z.string(), }); // In your client code const ws = new WebSocket("ws://localhost:3000/ws"); ws.onopen = () => { // Create a message with type-safe validation const joinMsg = createMessage(JoinRoomMessage, { roomId: "general" }); if (joinMsg.success) { ws.send(JSON.stringify(joinMsg.data)); } else { console.error("Invalid message:", joinMsg.error); } }; // Send a message with custom metadata function sendChatMessage(roomId: string, text: string) { const msg = createMessage( SendMessage, { roomId, text }, { correlationId: crypto.randomUUID() }, // Optional metadata ); if (msg.success) { ws.send(JSON.stringify(msg.data)); } } ``` -------------------------------- ### Real-time Notifications with WebSocket Router Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/examples.md This TypeScript code sets up a real-time notification system using Bun WebSocket Router. It defines message schemas for subscribing, unsubscribing, and receiving notifications, manages user subscriptions, and handles WebSocket connections. It also includes an HTTP API endpoint for broadcasting notifications. ```typescript import { WebSocketRouter, messageSchema, publish } from "bun-ws-router"; import { z } from "zod"; // Notification types enum NotificationType { INFO = "info", WARNING = "warning", ERROR = "error", SUCCESS = "success", } // Message schemas const SubscribeMessage = messageSchema( "SUBSCRIBE", z.object({ topics: z.array(z.string()).min(1), }), ); const UnsubscribeMessage = messageSchema( "UNSUBSCRIBE", z.object({ topics: z.array(z.string()).min(1), }), ); const NotificationMessage = messageSchema( "NOTIFICATION", z.object({ id: z.uuid(), type: z.nativeEnum(NotificationType), title: z.string(), message: z.string(), data: z.record(z.unknown()).optional(), timestamp: z.number(), }), ); // Track subscriptions const userSubscriptions = new Map>(); const router = new WebSocketRouter() .onOpen((ws) => { // Initialize user subscriptions userSubscriptions.set(ws.data.clientId, new Set()); // Subscribe to personal notifications ws.subscribe(`user:${ws.data.clientId}`); }) .onMessage(SubscribeMessage, (ctx) => { const { topics } = ctx.payload; const subs = userSubscriptions.get(ctx.clientId)!; // Subscribe to topics for (const topic of topics) { ctx.subscribe(`topic:${topic}`); subs.add(topic); } ctx.send({ type: "SUBSCRIBE_SUCCESS", payload: { topics }, }); }) .onMessage(UnsubscribeMessage, (ctx) => { const { topics } = ctx.payload; const subs = userSubscriptions.get(ctx.clientId)!; // Unsubscribe from topics for (const topic of topics) { ctx.unsubscribe(`topic:${topic}`); subs.delete(topic); } ctx.send({ type: "UNSUBSCRIBE_SUCCESS", payload: { topics }, }); }) .onClose((ws) => { // Clean up subscriptions userSubscriptions.delete(ws.data.clientId); }); // HTTP endpoint to send notifications const server = Bun.serve({ port: 3000, async fetch(req) { const url = new URL(req.url); // REST API to send notifications if (url.pathname === "/api/notify" && req.method === "POST") { const body = await req.json(); const notification = { id: crypto.randomUUID(), type: body.type || NotificationType.INFO, title: body.title, message: body.message, data: body.data, timestamp: Date.now(), }; // Broadcast to topic if (body.topic) { publish( server, `topic:${body.topic}`, NotificationMessage, notification, ); } // Send to specific user if (body.userId) { publish( server, `user:${body.userId}`, NotificationMessage, notification, ); } return Response.json({ success: true, id: notification.id }); } // WebSocket upgrade if (req.headers.get("upgrade") === "websocket") { return server.upgrade(req) ? undefined : new Response("WebSocket upgrade failed", { status: 426 }); } return new Response("Notification Server"); }, websocket: router.handlers(), }); console.log("Notification server running on http://localhost:3000"); ``` -------------------------------- ### Type-Safe WebSocket Client with Bun WS Router Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/examples.md Implements a client-side WebSocket connection using Bun WS Router for type-safe message handling. It defines message schemas for connection, chat messages, and typing indicators, and provides a ChatClient class to manage the WebSocket lifecycle, including sending messages, handling incoming data, and reconnecting on failure. Dependencies include 'bun-ws-router' and 'zod'. ```typescript import { createMessage, messageSchema } from "bun-ws-router"; import { z } from "zod"; // Share these schemas between client and server const ConnectionMessage = messageSchema( "CONNECTION", z.object({ token: z.string(), }), ); const ChatMessage = messageSchema( "CHAT_MESSAGE", z.object({ roomId: z.string(), text: z.string().min(1).max(500), }), ); const TypingMessage = messageSchema( "TYPING", z.object({ roomId: z.string(), isTyping: z.boolean(), }), ); // Client implementation class ChatClient { private ws: WebSocket; private reconnectTimer?: Timer; private messageQueue: Array<{ schema: any; payload: any; meta?: any }> = []; constructor( private url: string, private token: string, ) { this.connect(); } private connect() { this.ws = new WebSocket(this.url); this.ws.onopen = () => { console.log("Connected to chat server"); // Authenticate on connection const authMsg = createMessage(ConnectionMessage, { token: this.token }); if (authMsg.success) { this.ws.send(JSON.stringify(authMsg.data)); } // Send any queued messages this.flushMessageQueue(); }; this.ws.onmessage = (event) => { try { const message = JSON.parse(event.data); this.handleMessage(message); } catch (error) { console.error("Failed to parse message:", error); } }; this.ws.onclose = () => { console.log("Disconnected from server"); this.scheduleReconnect(); }; this.ws.onerror = (error) => { console.error("WebSocket error:", error); }; } private handleMessage(message: any) { switch (message.type) { case "CHAT_MESSAGE": this.onChatMessage?.(message.payload); break; case "TYPING": this.onTypingUpdate?.(message.payload); break; case "ERROR": this.onError?.(message.payload); break; } } sendMessage(roomId: string, text: string) { const msg = createMessage( ChatMessage, { roomId, text }, { correlationId: crypto.randomUUID() }, ); if (!msg.success) { console.error("Invalid message:", msg.error); return false; } if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(msg.data)); return true; } else { // Queue message for later this.messageQueue.push({ schema: ChatMessage, payload: { roomId, text }, meta: { correlationId: crypto.randomUUID() }, }); return false; } } setTyping(roomId: string, isTyping: boolean) { const msg = createMessage(TypingMessage, { roomId, isTyping }); if (msg.success && this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(msg.data)); } } private flushMessageQueue() { while (this.messageQueue.length > 0) { const { schema, payload, meta } = this.messageQueue.shift()!; const msg = createMessage(schema, payload, meta); if (msg.success) { this.ws.send(JSON.stringify(msg.data)); } } } private scheduleReconnect() { if (this.reconnectTimer) return; this.reconnectTimer = setTimeout(() => { this.reconnectTimer = undefined; this.connect(); }, 5000); } disconnect() { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = undefined; } this.ws.close(); } // Event handlers (to be set by consumer) onChatMessage?: (payload: any) => void; onTypingUpdate?: (payload: any) => void; onError?: (error: any) => void; } // Usage const client = new ChatClient("ws://localhost:3000/ws", "auth-token"); client.onChatMessage = (message) => { console.log("New message:", message); }; client.onError = (error) => { console.error("Chat error:", error); }; // Send a message client.sendMessage("general", "Hello everyone!"); // Show typing indicator client.setTyping("general", true); ``` -------------------------------- ### Graceful Shutdown Implementation Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/deployment.md Handles SIGTERM and SIGINT signals to initiate a graceful shutdown of the server. It stops accepting new connections, closes existing WebSocket connections, and cleans up resources like Redis connections before exiting. ```typescript let isShuttingDown = false; async function gracefulShutdown() { if (isShuttingDown) return; isShuttingDown = true; logger.info("Starting graceful shutdown..."); // Stop accepting new connections server.stop(); // Close existing connections for (const [clientId, ws] of connections) { ws.close(1001, "Server shutting down"); } // Wait for connections to close await new Promise((resolve) => setTimeout(resolve, 5000)); // Clean up resources await redis.quit(); logger.info("Graceful shutdown complete"); process.exit(0); } process.on("SIGTERM", gracefulShutdown); process.on("SIGINT", gracefulShutdown); ``` -------------------------------- ### JWT Authentication and Role-Based Access Control with Bun WS Router Source: https://github.com/kriasoft/bun-ws-router/blob/main/docs/examples.md This snippet demonstrates a complete WebSocket authentication and authorization flow. It uses JWT for verifying user identity and Zod for schema validation. The router handles connection opening, message authentication, and role-based access for admin actions. It includes user data management, channel subscriptions, and error handling. ```typescript import { WebSocketRouter, messageSchema, ErrorCode } from "bun-ws-router"; import { z } from "zod"; import jwt from "jsonwebtoken"; // User roles enum Role { USER = "user", ADMIN = "admin", MODERATOR = "moderator", } // Message schemas const AuthMessage = messageSchema( "AUTH", z.object({ token: z.string(), }), ); const AdminActionMessage = messageSchema( "ADMIN_ACTION", z.object({ action: z.enum(["kick", "ban", "mute"]), targetUserId: z.string(), reason: z.string().optional(), }), ); // User data interface interface UserData { userId: string; username: string; roles: Role[]; authenticated: boolean; } // Create router const router = new WebSocketRouter() .onOpen((ws) => { // Initialize as unauthenticated ws.data.user = { userId: "", username: "", roles: [], authenticated: false, }; // Give client time to authenticate setTimeout(() => { if (!ws.data.user?.authenticated) { ws.close(1008, "Authentication required"); } }, 5000); }) .onMessage(AuthMessage, async (ctx) => { try { // Verify JWT token const decoded = jwt.verify( ctx.payload.token, process.env.JWT_SECRET!, ) as any; // Update user data ctx.setData({ userId: decoded.userId, username: decoded.username, roles: decoded.roles || [Role.USER], authenticated: true, }); // Subscribe to user-specific channel ctx.subscribe(`user:${decoded.userId}`); // Subscribe to role channels for (const role of decoded.roles) { ctx.subscribe(`role:${role}`); } // Send success ctx.send({ type: "AUTH_SUCCESS", payload: { userId: decoded.userId, username: decoded.username, roles: decoded.roles, }, }); } catch (error) { ctx.send({ type: "ERROR", payload: { code: ErrorCode.UNAUTHORIZED, message: "Invalid token", }, }); // Close connection ctx.ws.close(1008, "Invalid token"); } }) .onMessage(AdminActionMessage, (ctx) => { const userData = ctx.getData(); // Check authentication if (!userData.authenticated) { ctx.send({ type: "ERROR", payload: { code: ErrorCode.UNAUTHORIZED, message: "Not authenticated", }, }); return; } // Check authorization if (!userData.roles.includes(Role.ADMIN)) { ctx.send({ type: "ERROR", payload: { code: ErrorCode.FORBIDDEN, message: "Admin access required", }, }); return; } // Perform admin action const { action, targetUserId, reason } = ctx.payload; switch (action) { case "kick": // Send kick message to target user ctx.publish(`user:${targetUserId}`, { type: "KICKED", payload: { reason }, }); break; case "ban": // Add to ban list (implement your logic) console.log(`Banning user ${targetUserId}`); break; case "mute": // Send mute notification ctx.publish(`user:${targetUserId}`, { type: "MUTED", payload: { reason }, }); break; } // Confirm action ctx.send({ type: "ADMIN_ACTION_SUCCESS", payload: { action, targetUserId, }, }); }); // Middleware to check auth on all messages router.onError((ws, error) => { console.error(`Error for client ${ws.data.clientId}:`, error); ws.send( JSON.stringify({ type: "ERROR", payload: { code: ErrorCode.INTERNAL_ERROR, message: "An error occurred", }, }), ); }); ```