Node.js - WebSocket support


Introduction

WebSocket is a communication protocol that provides full-duplex communication channels over a single TCP connection. This extension supports the following WebSocket libraries:

  • Native WebSocket API — built-in browser WebSocket constructor (also available in Node.js ≥ 22)
  • wsexternal link — the most widely used WebSocket library for Node.js
  • websocketexternal link npm package — WebSocket client and server implementation

Objects

Icon Metamodel name Description
TypeScript WebSocket Server Represents a WebSocket server endpoint identified by its path
TypeScript WebSocket Client Represents a WebSocket client connection identified by its URL
TypeScript Unknown WebSocket Client Represents a WebSocket client whose URL cannot be statically resolved

A WebSocketServer with name {} is created when the server path cannot be statically determined (for example when the path is passed as a runtime parameter). A server with path {} is treated as a catch-all endpoint.

Link type From To Created when
callLink TypeScript WebSocket Server TypeScript Function / Method A handler is registered on the server (on, route config)
callLink TypeScript Function / Method TypeScript WebSocket Server A function sends a message to clients (broadcast/publish) — see filtering rule below
callLink TypeScript Function / Method TypeScript WebSocket Client A function calls send() on a client connection — see filtering rule below
callLink TypeScript WebSocket Client TypeScript Function / Method A handler is registered on the client (on, onmessage, addEventListener)

A send link (callLink from a function to a WebSocket object) represents that the function actively pushes data to a WebSocket peer. In the call graph this makes the function a caller of the WebSocket server or client object, complementing the callee links that already show which functions receive messages from it.

Two kinds of send links are created:

  • Function → WebSocket Server: the function broadcasts or publishes a message to all connected clients (e.g. wss.clients.forEach, app.publish, wsServer.broadcast).
  • Function → WebSocket Client: the function sends a message over an outbound client connection (e.g. ws.send(data) called from application code that is not itself a message handler).

To avoid redundant links, send links are suppressed when the sending function already has a relationship with the WebSocket object through a handler registration:

Situation Example Result
The sending function is already a registered server handler (direct callee of the server) ws.send() inside a wss.on('connection', fn) callback Suppressed — fn is already linked as a callee of the server
The sending function is already a registered client event handler (direct callee of the client) ws.send() inside ws.onopen = fn or ws.addEventListener('message', fn) Suppressed — fn is already linked as a callee of the client
The sending function is not a registered handler wsServer.broadcast() called from a scheduler or REST endpoint Created — the send link appears in the call graph

In short: a send link is only created when the sending function has no existing relationship with the WebSocket object. This keeps the call graph clean and avoids showing the same function as both a callee (handler) and a caller (sender) of the same object.

Supported API methods

ws library

Server side

API method Object created / linked Link type
new WebSocketServer({ path }) TypeScript WebSocket Server
wss.on('connection', fn) fn linked as callee of server callLink
ws.send(data) called from a registered handler suppressed (see filtering rule)
wss.clients.forEach(fn) + client.send(data) fn linked as caller (send link) callLink

Client side

API method Object created / linked Link type
new WebSocket(url) TypeScript WebSocket Client
ws.on('message', fn) fn linked as callee of client callLink
ws.send(data) from a handler suppressed (see filtering rule)
ws.send(data) from other code fn linked as caller (send link) callLink

websocket npm package

Server side

API method Object created / linked Link type
new WebSocketServer({ httpServer }) TypeScript WebSocket Server
wsServer.on('request', fn) fn linked as callee of server callLink
conn.send(), conn.sendUTF(), conn.sendBytes() from a request handler suppressed (see filtering rule)
wsServer.broadcast(), wsServer.broadcastUTF(), wsServer.broadcastBytes() from non-handler fn linked as caller (send link) callLink

Client side

API method Object created / linked Link type
new WebSocketClient() + client.connect(url) TypeScript WebSocket Client
client.on('connect', fn) fn linked as callee of client callLink
conn.send(), conn.sendUTF(), conn.sendBytes() from non-handler fn linked as caller (send link) callLink
new W3CWebSocket(url) TypeScript WebSocket Client
ws.onmessage = fn / ws.addEventListener(...) fn linked as callee of client callLink
ws.send(data) from a handler suppressed (see filtering rule)
ws.send(data) from other code fn linked as caller (send link) callLink

Native WebSocket API

Client side

API method Object created / linked Link type
new WebSocket(url) TypeScript WebSocket Client
ws.onmessage = fn fn linked as callee of client callLink
ws.onopen = fn fn linked as callee of client callLink
ws.onclose = fn / ws.onerror = fn fn linked as callee of client callLink
ws.addEventListener('message', fn) fn linked as callee of client callLink
ws.send(data) from a handler suppressed (see filtering rule)
ws.send(data) from other code fn linked as caller (send link) callLink

Example

When analyzing the following files

server.ts

import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080, path: '/chat' });

// callLink: wss ──→ connection_PARAM_1  (callee — server registers this handler)
wss.on('connection', (ws) => {
    // callLink: wss ──→ message_PARAM_1  (callee — server registers this inner handler)
    ws.on('message', (data) => {
        // No send link: this function is already a callee of the server,
        // so ws.send() is suppressed by the filtering rule.
        ws.send('echo: ' + data);
    });
});

// callLink: broadcastAll ──→ wss  (send link — broadcastAll is NOT a server handler)
function broadcastAll(msg: string) {
    wss.clients.forEach((client) => {
        client.send(msg);
    });
}

client.ts

const ws = new WebSocket('wss://example.com/chat');

// callLink: ws ──→ onMsg  (callee — client registers this handler)
ws.onmessage = function onMsg(event) {
    // No send link: onMsg is already a callee of the client,
    // so ws.send() is suppressed by the filtering rule.
    ws.send('ack');
};

// callLink: ws ──→ onOpen  (callee — client registers this handler)
ws.onopen = function onOpen() {
    // Also suppressed: onOpen is a callee of the client.
    ws.send(JSON.stringify({ action: 'subscribe' }));
};

// callLink: sendMessage ──→ ws  (send link — sendMessage is NOT a client handler)
function sendMessage(text: string) {
    ws.send(text);
}

you will get the following result:

Known limitations

  • Client-to-server linking works only when the WebSocket server endpoint is declared using the library’s built-in path option. Servers that perform path matching through custom upgrade-handling logic are not currently supported. The server path must exactly match the trailing path of the client URL. For example, a server configured with /chat matches a client connecting to ws://example.com/chat, but a server configured with / does not match a client connecting to ws://example.com/chat
  • Host and port are currently ignored during linking, which may result in false links between clients and servers running on different hosts and/or ports.