Skip to content

WebSocket ToolkitBuild type-safe WebSocket APIs with confidence

Message routing, RPC, and broadcasting with Zod or Valibot validation for Bun, Cloudflare, browsers

WS-Kit

Quick Start โ€‹

Build a collaborative chat room in minutes with type-safe messages and broadcasting:

typescript
import { z, message, createRouter } from "@ws-kit/zod";
import { serve } from "@ws-kit/bun";

// Define message schemas โ€” fully typed end-to-end
const JoinRoom = message("JOIN_ROOM", { roomId: z.string() });
const SendMessage = message("SEND_MESSAGE", { text: z.string() });
const RoomUpdate = message("ROOM_UPDATE", {
  userId: z.string(),
  action: z.enum(["joined", "left"]),
  messageCount: z.number(),
});

// Create type-safe router with user context
type AppData = { userId?: string; roomId?: string };
const router = createRouter<AppData>();

// Handle room joins with pub/sub
router.on(JoinRoom, async (ctx) => {
  ctx.assignData({ roomId: ctx.payload.roomId });
  ctx.subscribe(ctx.payload.roomId); // Join topic

  // Broadcast to all room subscribers (type-safe!)
  await router.publish(ctx.payload.roomId, RoomUpdate, {
    userId: ctx.ws.data.userId || "anonymous",
    action: "joined",
    messageCount: 1,
  });
});

// Handle messages with full type inference
router.on(SendMessage, async (ctx) => {
  const roomId = ctx.ws.data?.roomId;
  await router.publish(roomId, RoomUpdate, {
    userId: ctx.ws.data?.userId || "anonymous",
    action: "joined",
    messageCount: 2, // In real app, track actual count
  });
});

// Authenticate and serve
serve(router, {
  port: 3000,
  authenticate(req) {
    const userId = req.headers.get("x-user-id");
    return userId ? { userId } : undefined;
  },
});

Type-safe messaging, pub/sub broadcasting, and authentication โ€” all built-in. Explore more examples โ†’