Skip to content

Type-safe WebSockets for TypeScript, end to end.

Declare your messages once. chanx-js routes, validates, broadcasts and documents them on the server, and generates a typed client for the browser, your framework or another Node service.

Quick start ​

Install the server, with ws for Node's HTTP server and Zod for payloads:

bash
npm install @chanx-js/server ws zod

Then the client, and generate its types from the running server:

bash
npm install @chanx-js/client
# writes src/generated
npx @chanx-js/codegen http://localhost:8000/asyncapi.json
ts
import { createServer } from 'node:http';
import { z } from 'zod';
import {
  PingMessage,
  PongMessage,
  consumer,
  createChanx,
  defineMessage,
} from '@chanx-js/server';
import {
  asyncapiMiddleware,
  attachChanx,
} from '@chanx-js/server/node';

const Chat = defineMessage('chat', z.object({ text: z.string() }));
const Notice = defineMessage(
  'notice',
  z.object({ text: z.string() }),
);

const chat = consumer('/ws/chat/{room}', {
  name: 'chat',
  emits: [Notice],
  groups: (conn) => [`room.${conn.params.room}`],
})
  .on(PingMessage, { output: PongMessage }, () =>
    PongMessage.create(),
  )
  .on(Chat, (m, ctx) =>
    ctx.broadcast(Notice.create({ text: m.payload.text })),
  );

const chanx = createChanx({ consumers: [chat] });
// docs at /asyncapi
const server = createServer(asyncapiMiddleware(chanx));
attachChanx(server, chanx);
server.listen(8000);
ts
import { createClient } from '@chanx-js/client';
import { chat } from './src/generated/index.js';

const client = createClient({ baseUrl: 'ws://localhost:8000' });
const connection = client.connect(chat, {
  params: { room: 'lobby' },
});

connection.on('notice', (message) =>
  console.log(message.payload.text),
);
// typed: PongMessage
const pong = await connection.request({ action: 'ping' });
connection.send({ action: 'chat', payload: { text: 'hello' } });

Run the server, then the client: it prints hello, broadcast back to everyone in lobby. From here, build out the server or use the client in your framework.

MIT licensed