Socket.IO -- Setup & Configuration
Real-time, bidirectional, event-based communication using Socket.IO -- with automatic runtime detection for both Node.js and Bun.
Quick Reference
| Item | Value |
|---|---|
| Package | @venizia/ignis (core) |
| Class | SocketIOComponent |
| Server Helper | SocketIOServerHelper |
| Client Helper | SocketIOClientHelper |
| Runtimes | Node.js (@hono/node-server) and Bun (native) |
| Scaling | @socket.io/redis-adapter + @socket.io/redis-emitter |
Import Paths
IMPORTANT
SocketIOComponent and SocketIOBindingKeys are not exported from the @venizia/ignis barrel. You must import from the @venizia/ignis/socket-io subpath.
// From core -- subpath import (NOT from '@venizia/ignis')
import {
SocketIOComponent,
SocketIOBindingKeys,
} from '@venizia/ignis/socket-io';
// From helpers -- subpath import
import {
SocketIOServerHelper,
SocketIOClientHelper,
SocketIOConstants,
SocketIOClientStates,
} from '@venizia/ignis-helpers/socket-io';
// Types from helpers subpath
import type {
TSocketIOAuthenticateFn,
TSocketIOValidateRoomFn,
TSocketIOClientConnectedFn,
ISocketIOClientOptions,
IOptions,
TSocketIOEventHandler,
TSocketIOClientState,
} from '@venizia/ignis-helpers/socket-io';Use Cases
- Live notifications and alerts
- Real-time chat and messaging
- Collaborative editing (docs, whiteboards)
- Live data streams (dashboards, monitoring)
- Multiplayer game state synchronization
- Service-to-service real-time communication (via
SocketIOClientHelper)
Server Helper Setup
Step 1: Install Dependencies
# Core dependency (already included via @venizia/ignis)
# ioredis is required for the Redis adapter
# For Bun runtime only -- optional peer dependency
bun add @socket.io/bun-engineStep 2: Bind Required Services
In your application's preConfigure() method, bind the required services and register the component:
import { BaseApplication } from '@venizia/ignis';
import {
SocketIOComponent,
SocketIOBindingKeys,
} from '@venizia/ignis/socket-io';
import { RedisHelper, ValueOrPromise } from '@venizia/ignis-helpers';
import type {
TSocketIOAuthenticateFn,
TSocketIOValidateRoomFn,
TSocketIOClientConnectedFn,
} from '@venizia/ignis-helpers/socket-io';
export class Application extends BaseApplication {
private redisHelper: RedisHelper;
preConfigure(): ValueOrPromise<void> {
this.setupSocketIO();
// ... other setup
}
setupSocketIO() {
// 1. Redis connection (required for adapter + emitter)
this.redisHelper = new RedisHelper({
name: 'socket-io-redis',
host: process.env.REDIS_HOST ?? 'localhost',
port: +(process.env.REDIS_PORT ?? 6379),
password: process.env.REDIS_PASSWORD,
autoConnect: false,
});
this.bind<RedisHelper>({
key: SocketIOBindingKeys.REDIS_CONNECTION,
}).toValue(this.redisHelper);
// 2. Authentication handler (required)
const authenticateFn: TSocketIOAuthenticateFn = handshake => {
const token = handshake.headers.authorization;
// Implement your auth logic -- JWT verification, session check, etc.
return !!token;
};
this.bind<TSocketIOAuthenticateFn>({
key: SocketIOBindingKeys.AUTHENTICATE_HANDLER,
}).toValue(authenticateFn);
// 3. Room validation handler (optional -- joins rejected without this)
const validateRoomFn: TSocketIOValidateRoomFn = ({ socket, rooms }) => {
// Return the rooms that the client is allowed to join
const allowedRooms = rooms.filter(room => room.startsWith('public-'));
return allowedRooms;
};
this.bind<TSocketIOValidateRoomFn>({
key: SocketIOBindingKeys.VALIDATE_ROOM_HANDLER,
}).toValue(validateRoomFn);
// 4. Client connected handler (optional)
const clientConnectedFn: TSocketIOClientConnectedFn = ({ socket }) => {
console.log('Client connected:', socket.id);
// Register custom event handlers on the socket
};
this.bind<TSocketIOClientConnectedFn>({
key: SocketIOBindingKeys.CLIENT_CONNECTED_HANDLER,
}).toValue(clientConnectedFn);
// 5. Register the component -- that's it!
this.component(SocketIOComponent);
}
}autoConnect: false Rationale
The RedisHelper is created with autoConnect: false because the server helper internally calls client.duplicate() to create 3 independent Redis connections (pub, sub, emitter). The duplicated clients inherit the lazyConnect setting from the parent. During configure(), the helper detects clients in wait status and explicitly calls client.connect() on each, then awaits all 3 to reach ready status before proceeding. This avoids race conditions where the parent connects before the duplicates are created.
Redis Connection Alternatives
You can use either RedisHelper (single Redis instance) or RedisClusterHelper (Redis Cluster mode). Both extend DefaultRedisHelper, which is the type the component validates against:
import { RedisClusterHelper } from '@venizia/ignis-helpers';
// For Redis Cluster deployments
const redisHelper = new RedisClusterHelper({
name: 'socket-io-redis-cluster',
nodes: [
{ host: 'redis-node-1', port: 6379 },
{ host: 'redis-node-2', port: 6380 },
{ host: 'redis-node-3', port: 6381 },
],
password: process.env.REDIS_PASSWORD,
autoConnect: false,
});
this.bind<RedisClusterHelper>({
key: SocketIOBindingKeys.REDIS_CONNECTION,
}).toValue(redisHelper);The internal TRedisClient type is Redis | Cluster, so both ioredis connection types are supported transparently.
Configuration
Default Server Options
The component applies these defaults if SocketIOBindingKeys.SERVER_OPTIONS is not bound or partially overridden:
| Option | Default | Description |
|---|---|---|
identifier | 'SOCKET_IO_SERVER' | Unique identifier for the helper instance |
path | '/io' | URL path for Socket.IO handshake/polling |
cors.origin | '*' | Allowed origins (restrict in production!) |
cors.methods | ['GET', 'POST'] | Allowed HTTP methods for CORS preflight |
cors.preflightContinue | false | Pass preflight to next handler |
cors.optionsSuccessStatus | 204 | Status code for successful OPTIONS requests |
cors.credentials | true | Allow cookies/auth headers |
perMessageDeflate.threshold | 4096 | Minimum message size to compress (bytes) |
perMessageDeflate.concurrencyLimit | 20 | Max concurrent compression operations |
perMessageDeflate.clientNoContextTakeover | true | Client releases compression context after each message |
perMessageDeflate.serverNoContextTakeover | true | Server releases compression context after each message |
perMessageDeflate.serverMaxWindowBits | 10 | Server-side maximum window size (2^10 = 1KB) |
WARNING
The default cors.origin: '*' is suitable for development only. In production, restrict this to your specific domains.
Full DEFAULT_SERVER_OPTIONS
const DEFAULT_SERVER_OPTIONS: Partial<IServerOptions> = {
identifier: 'SOCKET_IO_SERVER',
path: '/io',
cors: {
origin: '*',
methods: ['GET', 'POST'],
preflightContinue: false,
optionsSuccessStatus: 204,
credentials: true,
},
perMessageDeflate: {
threshold: 4096,
zlibDeflateOptions: { chunkSize: 10 * 1024 },
zlibInflateOptions: { windowBits: 12, memLevel: 8 },
clientNoContextTakeover: true,
serverNoContextTakeover: true,
serverMaxWindowBits: 10,
concurrencyLimit: 20,
},
};Custom Configuration
Bind custom server options before registering the component:
import { SocketIOBindingKeys } from '@venizia/ignis/socket-io';
import type { ServerOptions } from 'socket.io';
const customOptions: Partial<ServerOptions> = {
path: '/socket.io',
cors: {
origin: ['https://myapp.com', 'https://admin.myapp.com'],
methods: ['GET', 'POST'],
credentials: true,
},
pingTimeout: 60000,
pingInterval: 25000,
maxHttpBufferSize: 1e6, // 1MB
};
this.bind<Partial<ServerOptions>>({
key: SocketIOBindingKeys.SERVER_OPTIONS,
}).toValue(customOptions);
this.component(SocketIOComponent);NOTE
The identifier field is part of the component's IServerOptions interface (which extends ServerOptions), not Socket.IO's native options. To set the identifier, include it in the bound options object.
Binding Keys
All binding keys are available in SocketIOBindingKeys:
| Binding Key | Constant | Type | Required | Default |
|---|---|---|---|---|
@app/socket-io/server-options | SERVER_OPTIONS | Partial<ServerOptions> | No | See defaults above |
@app/socket-io/redis-connection | REDIS_CONNECTION | RedisHelper / RedisClusterHelper / DefaultRedisHelper | Yes | null |
@app/socket-io/authenticate-handler | AUTHENTICATE_HANDLER | TSocketIOAuthenticateFn | Yes | null |
@app/socket-io/validate-room-handler | VALIDATE_ROOM_HANDLER | TSocketIOValidateRoomFn | No | null |
@app/socket-io/client-connected-handler | CLIENT_CONNECTED_HANDLER | TSocketIOClientConnectedFn | No | null |
@app/socket-io/instance | SOCKET_IO_INSTANCE | SocketIOServerHelper | -- | Set by component |
NOTE
SOCKET_IO_INSTANCE is not set by you -- the component creates and binds it automatically after the server starts. Inject it in services/controllers to interact with Socket.IO.
Constants
Constants are exported from @venizia/ignis-helpers/socket-io and used internally by both the component and the helper.
System Events
| Constant | Value | Description |
|---|---|---|
SocketIOConstants.EVENT_PING | 'ping' | Keep-alive ping emitted at pingInterval (default: 30s) |
SocketIOConstants.EVENT_CONNECT | 'connection' | New client connected (server-side event) |
SocketIOConstants.EVENT_DISCONNECT | 'disconnect' | Client disconnected |
SocketIOConstants.EVENT_JOIN | 'join' | Client requests to join room(s) |
SocketIOConstants.EVENT_LEAVE | 'leave' | Client requests to leave room(s) |
SocketIOConstants.EVENT_AUTHENTICATE | 'authenticate' | Client sends auth credentials |
SocketIOConstants.EVENT_AUTHENTICATED | 'authenticated' | Auth success response sent to client |
SocketIOConstants.EVENT_UNAUTHENTICATE | 'unauthenticated' | Auth failure response sent to client |
Default Rooms
All authenticated clients are automatically joined to these rooms:
| Constant | Value | Description |
|---|---|---|
SocketIOConstants.ROOM_DEFAULT | 'io-default' | Default room all authenticated clients join |
SocketIOConstants.ROOM_NOTIFICATION | 'io-notification' | Notification broadcast room |
TIP
You can override default rooms via the defaultRooms option on SocketIOServerHelper. The component uses the defaults above when not overridden.
Internal Constants (Server Helper)
These constants are defined at module scope in the server helper and are not exported, but they govern default behavior:
| Constant | Value | Description |
|---|---|---|
CLIENT_AUTHENTICATE_TIMEOUT | 10_000 (10s) | Time allowed for a client to authenticate before forced disconnect |
CLIENT_PING_INTERVAL | 30_000 (30s) | Interval between server-to-client ping emissions |
Both can be overridden via the authenticateTimeout and pingInterval constructor options on SocketIOServerHelper.
Client States
Each connected client tracks an authentication state that governs what actions are permitted:
| State | Constant | Description |
|---|---|---|
unauthorized | SocketIOClientStates.UNAUTHORIZED | Initial state -- client must emit authenticate within the timeout (default: 10s) |
authenticating | SocketIOClientStates.AUTHENTICATING | Auth in progress -- authenticateFn is executing |
authenticated | SocketIOClientStates.AUTHENTICATED | Auth successful -- client can send/receive events and join rooms |
State Machine Diagram
+------------------+
connect ---------->| unauthorized |
+--------+---------+
| emit('authenticate')
+--------v---------+
| authenticating |
+---+----------+---+
success | | failure
+---------v--+ +-------v-----------+
|authenticated| | unauthorized |--> disconnect
+-------------+ +------------------+
^
timeout (10s)SocketIOClientStates Source
export class SocketIOClientStates {
static readonly UNAUTHORIZED = 'unauthorized';
static readonly AUTHENTICATING = 'authenticating';
static readonly AUTHENTICATED = 'authenticated';
static readonly SCHEME_SET = new Set([
this.UNAUTHORIZED,
this.AUTHENTICATING,
this.AUTHENTICATED,
]);
static isValid(input: string): input is TConstValue<typeof SocketIOClientStates> {
return this.SCHEME_SET.has(input);
}
}Resolved Bindings
The component resolves all binding keys into a single IResolvedBindings object during the binding() phase:
IResolvedBindings Interface
interface IResolvedBindings {
redisConnection: DefaultRedisHelper;
authenticateFn: TSocketIOAuthenticateFn;
validateRoomFn?: TSocketIOValidateRoomFn;
clientConnectedFn?: TSocketIOClientConnectedFn;
}Callback Type Signatures
// Called with the socket handshake -- return true to authenticate, false to reject
type TSocketIOAuthenticateFn = (args: IHandshake) => ValueOrPromise<boolean>;
// Called when client emits 'join' -- return the subset of rooms the client is allowed to join
type TSocketIOValidateRoomFn = (opts: {
socket: IOSocket;
rooms: string[];
}) => ValueOrPromise<string[]>;
// Called after successful authentication -- register custom event handlers here
type TSocketIOClientConnectedFn = (opts: { socket: IOSocket }) => ValueOrPromise<void>;IHandshake Interface
interface IHandshake {
headers: IncomingHttpHeaders;
time: string;
address: string;
xdomain: boolean;
secure: boolean;
issued: number;
url: string;
query: ParsedUrlQuery;
auth: { [key: string]: any };
}See Also
- Usage & Examples -- Server-side usage, client helper, advanced patterns
- API Reference -- Architecture, method signatures, internals, types
- Error Reference -- Error conditions and troubleshooting
- Guides:
- Components Overview -- Component system basics
- Application -- Registering components
- Components:
- Components Index -- All built-in components
- Helpers:
- Socket.IO Helper -- Full
SocketIOServerHelper+SocketIOClientHelperAPI reference
- Socket.IO Helper -- Full
- External Resources:
- Socket.IO Documentation -- Official docs
- Socket.IO Redis Adapter -- Horizontal scaling guide
- @socket.io/bun-engine -- Bun runtime support
- Tutorials:
- Real-Time Chat -- Building a chat app with Socket.IO
- Changelog:
- 2026-02-06: Socket.IO Integration Fix -- Lifecycle timing fix + Bun runtime support