Socket.IO
SocketIOServerHelper and SocketIOClientHelper wrap socket.io with a mandatory post-connection authentication handshake, room management, and a Redis adapter so events reach clients no matter which server instance they are connected to.
In one example
The smallest working server: construct with a Redis connection and an authenticateFn, then configure().
import { createServer } from 'node:http';
import { RedisSingleHelper } from '@venizia/ignis-helpers';
import { SocketIOServerHelper } from '@venizia/ignis-helpers/socket-io';
const httpServer = createServer();
const redisHelper = new RedisSingleHelper({
name: 'socket-redis',
host: 'localhost',
port: 6379,
password: '',
});
const socketServer = new SocketIOServerHelper({
identifier: 'my-socket-server',
runtime: 'node',
server: httpServer,
redisConnection: redisHelper,
serverOptions: { cors: { origin: '*' } },
authenticateFn: async handshake => !!handshake.auth?.token,
});
await socketServer.configure();
httpServer.listen(3000);A client connects at the transport level, then must explicitly authenticate before it can join rooms or exchange events:
import { SocketIOClientHelper } from '@venizia/ignis-helpers/socket-io';
const client = new SocketIOClientHelper({
identifier: 'app-client',
host: 'http://localhost:3000',
options: {
path: '/socket.io',
extraHeaders: { Authorization: 'Bearer my-jwt-token' },
},
onConnected: () => client.authenticate(),
onAuthenticated: () => client.emit({ topic: 'ready', data: {} }),
});How it works
- Two independent helpers.
SocketIOServerHelperwraps asocket.ioServer;SocketIOClientHelperwraps asocket.io-clientSocket. Both extendBaseHelper, and the client can talk to anysocket.ioserver, not only this one. - Runtime-agnostic server. Pass a Node.js
http.Serverforruntime: 'node', or an@socket.io/bun-engineinstance forruntime: 'bun'. - Redis is mandatory server-side.
configure()duplicates the parentredisConnectioninto three dedicated clients:redisPub/redisSubpower@socket.io/redis-adapterfor cross-instance room broadcast, andredisEmitterpowers@socket.io/redis-emitterforsend(). - Boot fails fast on a broken Redis connection.
configure()waits for all three clients to reachreadyand rejects after 30 seconds if any never do, so a broken Redis connection fails boot instead of hanging it. - Authentication is a step separate from connecting. A client connects at the transport level in state
UNAUTHORIZED, then must emit'authenticate'. The server callsauthenticateFn(handshake); onlytruemoves the client toAUTHENTICATEDand joins it todefaultRooms. - Unauthenticated clients time out. A client that never authenticates within
authenticateTimeoutis disconnected - including one whoseauthenticateFnis still pending when the timeout fires. - Heartbeat has no pong check. Once authenticated, the server pings on
pingIntervalas a keep-alive; a silently dead connection is only caught when the underlying transport itself notices. - Custom rooms need
validateRoomFn. Room joins beyonddefaultRoomsare rejected unless you supply it - without it, every custom join request is dropped.
Defaults
| Option | Default | Purpose |
|---|---|---|
authenticateTimeout | 10s | Disconnects a client that never authenticates |
pingInterval | 30s | Server heartbeat interval once authenticated |
Common tasks
Send a message from the server
send() goes through the Redis emitter, so it reaches the target on any server instance. Omit destination to broadcast to everyone.
socketServer.send({
destination: 'some-room', // socket ID, room name, or omitted to broadcast
payload: { topic: 'notification', data: { message: 'Hello!' } },
callback: () => console.log('queued'),
});Listen for a custom event
Register on the server with on(); subscribe on the client with subscribe().
socketServer.on({
topic: 'custom-event',
handler: (data: { userId: string }) => console.log('received:', data),
});
client.subscribe({
event: 'notification',
handler: data => console.log('notification:', data),
});Manage rooms
Clients request rooms with joinRooms() / leaveRooms(); the server filters join requests through validateRoomFn.
const socketServer = new SocketIOServerHelper({
// ...
defaultRooms: ['general', 'announcements'],
validateRoomFn: async ({ socket, rooms }) => rooms.filter(room => room.startsWith('public-')),
});
client.joinRooms({ rooms: ['public-chat'] });
client.leaveRooms({ rooms: ['public-chat'] });Emit from the client
client.emit({
topic: 'chat-message',
data: { text: 'Hello, world!' },
callback: () => console.log('emit completed'),
});Shut down cleanly
await socketServer.shutdown(); // disconnects clients, closes IO server, quits all 3 Redis clients
client.shutdown(); // removes listeners, disconnects, resets stateSee also
- Full reference - every method signature, type, constant, and error case
- Socket.IO Component - DI-managed lifecycle wrapper around this helper
- WebSocket Helper - Bun-native alternative with no
socket.iodependency - Redis Helper -
RedisSingleHelper/RedisClusterHelperused asredisConnection
Files:
packages/helpers/src/modules/socket/socket-io/server/helper.ts-SocketIOServerHelperpackages/helpers/src/modules/socket/socket-io/client/helper.ts-SocketIOClientHelperpackages/helpers/src/modules/socket/socket-io/common/types.ts- option and callback typespackages/helpers/src/modules/socket/socket-io/common/constants.ts-SocketIOConstants,SocketIOClientStates