WebSocket
Contract
@modularityjs/ws provides a decorator-based WebSocket gateway system with typed message handling, connection lifecycle hooks, and parameter injection.
Gateways
Gateways are classes that handle WebSocket connections on a specific path. The mounted URL is the configured WsConfig.path prefix (default /ws) concatenated with the gateway path — so @WsGateway('/chat') with the default config is served at /ws/chat, not /chat.
import {
Client,
Data,
OnConnect,
OnDisconnect,
OnMessage,
WsGateway,
WsGatewaysPool,
} from '@modularityjs/ws';
import type { WsClient } from '@modularityjs/ws';
@WsGateway('/chat') // served at '/ws/chat' with default WsConfig.path
class ChatGateway {
@OnConnect()
handleConnect(@Client() client: WsClient) {
console.log(`Client ${client.id} connected`);
}
@OnDisconnect()
handleDisconnect(@Client() client: WsClient) {
console.log(`Client ${client.id} disconnected`);
}
@OnMessage('chat:send')
handleMessage(@Client() client: WsClient, @Data() data: { text: string }) {
console.log(`Message from ${client.id}: ${data.text}`);
}
}Register gateways via the WsGatewaysPool:
@Module({
name: 'chat',
imports: [WsModule],
providers: [ChatGateway],
pools: [
{
pool: WsGatewaysPool,
key: 'chat-gateway',
useClass: ChatGateway,
},
],
})
class ChatModule {}Decorators
Class Decorators
| Decorator | Description |
|---|---|
@WsGateway(path) | Marks a class as a WebSocket gateway. The mounted URL is WsConfig.path + path (default prefix /ws, so /ws + path). |
Method Decorators
| Decorator | Description |
|---|---|
@OnConnect() | Called when a client connects |
@OnDisconnect() | Called when a client disconnects |
@OnMessage(type) | Called when a message with the given type arrives |
Parameter Decorators
| Decorator | Description |
|---|---|
@Client() | Injects the WsClient instance |
@Data() | Injects the message data payload |
WsClient
Each connected client exposes:
interface WsClient {
readonly id: string;
send(data: unknown): void;
close(code?: number, reason?: string): void;
}WsMessage
Messages exchanged over the WebSocket follow a typed envelope:
interface WsMessage {
readonly type: string;
readonly data: unknown;
}WsServer
The abstract WsServer provides server-wide operations:
abstract class WsServer {
abstract broadcast(type: string, data: unknown): void;
abstract getClients(): ReadonlySet<WsClient>;
abstract close(): Promise<void>;
}Inject WsServer to broadcast messages to all connected clients:
import { Inject, Injectable } from '@modularityjs/di';
import { WsServer } from '@modularityjs/ws';
@Injectable()
class NotificationService {
constructor(@Inject(WsServer) private readonly ws: WsServer) {}
notifyAll(message: string): void {
this.ws.broadcast('notification', { message });
}
getOnlineCount(): number {
return this.ws.getClients().size;
}
}Drivers
Fastify (@modularityjs/ws-fastify)
Integrates with the existing Fastify HTTP server via @fastify/websocket. Requires @modularityjs/http-fastify.
import { HttpModule } from '@modularityjs/http';
import { WsModule } from '@modularityjs/ws';
import { HttpFastifyModule } from '@modularityjs/http-fastify';
import { WsFastifyModule } from '@modularityjs/ws-fastify';
const modules = [
HttpModule.forRoot({ port: 3000 }),
HttpFastifyModule,
WsModule,
WsFastifyModule,
];The driver registers the @fastify/websocket plugin on the Fastify instance, then walks every @WsGateway-decorated class and wires up routes and message handlers for the ones whose providers are bound in the DI container (i.e. registered via WsGatewaysPool). On shutdown, all client connections are closed gracefully.
Configuration
WsModule.forRoot({
path: '/ws', // URL prefix prepended to every gateway path (default '/ws')
maxClients: 1000, // hard cap on concurrent client sockets (default)
idleTimeoutMs: undefined, // close sockets idle longer than this; unset = no timeout
});path is a prefix, not a literal route. A gateway declared as @WsGateway('/chat') is served at path + '/chat' — so '/ws/chat' with the default, or '/chat' if path: ''.
Usage Example
A chat gateway that broadcasts messages to all connected clients (served at /ws/chat with the default WsConfig.path = '/ws'):
@WsGateway('/chat')
class ChatGateway {
constructor(@Inject(WsServer) private readonly server: WsServer) {}
@OnConnect()
handleConnect(@Client() client: WsClient) {
this.server.broadcast('system', {
text: `User ${client.id} joined`,
});
}
@OnMessage('chat:send')
handleMessage(@Client() client: WsClient, @Data() data: { text: string }) {
this.server.broadcast('chat:message', {
from: client.id,
text: data.text,
});
}
@OnDisconnect()
handleDisconnect(@Client() client: WsClient) {
this.server.broadcast('system', {
text: `User ${client.id} left`,
});
}
}Client-side messages use the { type, data } envelope:
{ "type": "chat:send", "data": { "text": "Hello, world!" } }