Define message contracts with Zod or Valibot and get end-to-end TypeScript safety for WebSocket RPC and pub/sub across Bun, Cloudflare, Node.js, and browsers.
Docs → https://kriasoft.com/ws-kit/
- Type inference from schema to handler, errors, and client calls
- RPC + pub/sub + middleware + lifecycle hooks in one router
- Pluggable validators/adapters (Bun, Cloudflare, Redis, in-memory)
- Test harness with fake connections, clock, and event capture
- Universal client with auto-reconnect, retries, and offline queueing
# With Zod on Bun (recommended)
bun add @ws-kit/zod @ws-kit/bun
bun add zod bun @types/bun -D
# Valibot (smaller bundles)
bun add @ws-kit/valibot @ws-kit/bun
bun add valibot bun @types/bun -Dimport { z, message, createRouter, withZod } from "@ws-kit/zod";
import { serve } from "@ws-kit/bun";
const Ping = message("PING", { text: z.string() });
const Pong = message("PONG", { reply: z.string() });
const router = createRouter().plugin(withZod());
router.on(Ping, (ctx) => {
ctx.send(Pong, { reply: `Got: ${ctx.payload.text}` });
});
serve(router, {
port: 3000,
authenticate(req) {
const token = req.headers.get("authorization");
return token ? { userId: "u_123" } : undefined;
},
});import { rpc, message, wsClient } from "@ws-kit/client/zod";
import { z } from "@ws-kit/zod";
const Hello = rpc("HELLO", { name: z.string() }, "HELLO_OK", {
text: z.string(),
});
const Broadcast = message("BROADCAST", { data: z.string() });
const client = wsClient({ url: "ws://localhost:3000" });
await client.connect();
const reply = await client.request(Hello, { name: "Ada" });
console.log(reply.payload.text); // typed as string
client.on(Broadcast, (msg) => {
console.log(msg.payload.data);
});- Docs: https://kriasoft.com/ws-kit/
- Examples:
examples/ - Packages:
packages/ - Support: Discord
MIT







