Type-Safe Routing
Define message schemas with Zod or Valibot and get full TypeScript type inference from schema to handler without type assertions
Message routing, RPC, and broadcasting with Zod or Valibot validation for Bun, Cloudflare, browsers
Build a collaborative chat room in minutes with type-safe messages and broadcasting:
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 โ