Skip to content

Server WebSocket

ServerWebSocketActor runs a server. Clients connect from the outside; the actor accepts each connection, spawns a per-connection child handler, and routes inbound messages there.

import { ActorSystem, Props, ServerWebSocketActor, Actor } from 'actor-ts';
class Connection extends Actor<WsConnInbound> {
constructor(public readonly id: string) { super(); }
override onReceive(msg: WsConnInbound): void {
if (msg.kind === 'opened') this.log.info(`connection opened: ${this.id}`);
if (msg.kind === 'text') this.log.info(`from ${this.id}: ${msg.payload}`);
if (msg.kind === 'closed') this.log.info(`connection closed: ${this.id}`);
}
}
const server = system.actorOf(
Props.create(() => new ServerWebSocketActor({
host: '0.0.0.0',
port: 8080,
onConnection: (id) => Props.create(() => new Connection(id)),
})),
'ws-server',
);

Each incoming WS connection spawns a Connection child. Messages on that connection arrive as WsConnInbound; the framework death-watches the connection actor so its termination closes the underlying socket.

interface ServerWebSocketActorSettings extends BrokerCommonSettings {
host: string;
port: number;
onConnection: (id: string) => Props<WsConnInbound>;
backend?: 'bun' | 'ws' | 'fastify'; // default auto-detect
path?: string; // restrict to specific path
tls?: TlsOptions;
}

onConnection is called for each new connection. Return a Props for the per-connection actor — usually a class that holds the connection’s id + state.

{ backend: 'bun' } // Bun's built-in WS server
{ backend: 'ws' } // Node 'ws' package (default for Node)
{ backend: 'fastify' } // attached to an existing Fastify server
BackendRuntime fit
'bun'Bun-only. Uses Bun.serve’s native WebSocket.
'ws'Node + Bun. Uses the ws npm package.
'fastify'Sharing a port with an HTTP server, via @fastify/websocket.

Auto-detection picks 'bun' on Bun, 'ws' on Node.

// Per-connection actor sends back via its parent:
override onReceive(msg: WsConnInbound) {
if (msg.kind === 'text') {
msg.respond('echo: ' + msg.payload);
// or: msg.respondBinary(uint8array);
}
}

The framework attaches respond(text) / respondBinary(bytes) / close() methods to each inbound message. Easy request-response within a single connection.

For broadcast (send to every connected client), the parent actor tracks the child set:

// Inside the server actor:
this.broadcastText('event: ' + JSON.stringify({ ... }));

The framework’s broadcast* methods iterate every active connection.

new ServerWebSocketActor({
host: '0.0.0.0',
port: 8080,
path: '/feed', // only accept connections at /feed
onConnection: ...,
});

For multiple endpoints on the same port, spawn multiple ServerWebSocketActors with different paths, or run a single one and route inside onConnection based on the request URL.

Terminal window
npm install ws # for backend: 'ws'
# Bun has built-in WebSocket support

Three good fits:

  1. Real-time UIs — pushing updates to browser clients.
  2. Custom messaging protocols — game servers, chat backends.
  3. Webhooks-over-WS — replacing polling with push.

For one-way streams from server to client, SSE is simpler. For request/reply RPC, HTTP (with WS for upgrade) is the norm.