Skip to content

Socket.IO -- Setup & Configuration

Real-time, bidirectional, event-based communication using Socket.IO -- with automatic runtime detection for Node.js and Bun, horizontal scaling via Redis, and a built-in authentication handshake.

Quick Reference

ItemValue
Package@venizia/ignis (core)
ClassSocketIOComponent
Server HelperSocketIOServerHelper
Client HelperSocketIOClientHelper
RuntimesNode.js (@hono/node-server) and Bun (native)
Scaling@socket.io/redis-adapter + @socket.io/redis-emitter

IMPORTANT

SocketIOComponent and SocketIOBindingKeys are not exported from the @venizia/ignis barrel -- import from the @venizia/ignis/socket-io subpath.

typescript
// From core -- subpath import (NOT from '@venizia/ignis')
import { SocketIOComponent, SocketIOBindingKeys } from '@venizia/ignis/socket-io';

// From helpers -- subpath import
import { SocketIOServerHelper, SocketIOClientHelper, SocketIOConstants } from '@venizia/ignis-helpers/socket-io';
import type { TSocketIOAuthenticateFn, TSocketIOValidateRoomFn } from '@venizia/ignis-helpers/socket-io';

Use cases:

  • Live notifications and alerts
  • Real-time chat and collaborative editing
  • Live dashboards and monitoring streams
  • Multiplayer game state synchronization
  • Service-to-service real-time messaging (via SocketIOClientHelper)

Setup

Three pieces are bound in preConfigure(), before the component itself is registered:

StepBinding keyRequired
1. Redis connectionSocketIOBindingKeys.REDIS_CONNECTIONYes
2. Authenticate handlerSocketIOBindingKeys.AUTHENTICATE_HANDLERYes
3. Room / connected handlersVALIDATE_ROOM_HANDLER, CLIENT_CONNECTED_HANDLERNo
typescript
import { BaseApplication } from '@venizia/ignis';
import { SocketIOComponent, SocketIOBindingKeys } from '@venizia/ignis/socket-io';
import { RedisSingleHelper, ValueOrPromise } from '@venizia/ignis-helpers';
import type { TSocketIOAuthenticateFn } from '@venizia/ignis-helpers/socket-io';

export class Application extends BaseApplication {
  preConfigure(): ValueOrPromise<void> {
    // 1. Redis connection -- required for the adapter + emitter
    const redisHelper = new RedisSingleHelper({
      name: 'socket-io-redis',
      host: process.env.REDIS_HOST ?? 'localhost',
      port: +(process.env.REDIS_PORT ?? 6379),
      autoConnect: false,
    });
    this.bind({ key: SocketIOBindingKeys.REDIS_CONNECTION }).toValue(redisHelper);

    // 2. Authentication handler -- required
    const authenticateFn: TSocketIOAuthenticateFn = handshake => !!handshake.headers.authorization;
    this.bind({ key: SocketIOBindingKeys.AUTHENTICATE_HANDLER }).toValue(authenticateFn);

    // 3. Register the component
    this.component(SocketIOComponent);
  }
}

WARNING

autoConnect: false is required on the Redis helper -- the server helper duplicates the connection into 3 independent clients and connects them itself during configure(). Connecting the parent first races against the duplicates. Full step-by-step setup (Bun peer dependency, room validation, cluster/sentinel Redis, the autoConnect rationale) is in Usage & Examples.

How It Works

  • Post-start hook, not immediate init. Socket.IO needs a running server, but components initialize before the server starts. binding() resolves all bindings and registers a post-start hook; the hook builds SocketIOServerHelper and binds it to SOCKET_IO_INSTANCE only after start() brings the server up.
  • Runtime detection picks the wiring. RuntimeModules.detect() selects Node.js (Socket.IO attaches to node:http.Server directly) or Bun (@socket.io/bun-engine is dynamically imported and wired into server.reload()). See the runtime matrix for the full comparison.
  • One Redis connection becomes three. The connection you bind is never consumed directly -- the helper calls duplicateClient() three times: a pub/sub pair for the Redis adapter (cross-instance room broadcast) and a third client for the Redis emitter (cross-instance direct send).
  • Authentication is mandatory. Every client starts unauthorized and must emit authenticate within authenticateTimeout (default 10s) or it is disconnected. Success joins the client to the default rooms and starts a keep-alive ping.
  • Room joins are opt-in by default. Without a bound VALIDATE_ROOM_HANDLER, every join request is silently rejected -- security-by-default, not a bug.

Common Tasks

Restrict CORS for production. Bind SERVER_OPTIONS before registering the component -- the default (cors.origin: '*') is for local development only.

typescript
import type { ServerOptions } from 'socket.io';

this.bind<Partial<ServerOptions>>({ key: SocketIOBindingKeys.SERVER_OPTIONS }).toValue({
  cors: { origin: ['https://myapp.com'], credentials: true },
});
this.component(SocketIOComponent);

Send a message from a service. SOCKET_IO_INSTANCE is bound by the component after the server starts, so resolve it lazily -- never @inject it in a constructor. Full pattern in Usage & Examples.

typescript
this.io.send({ destination: userId, payload: { topic: 'notification', data } });

Scale Redis beyond a single node. Swap RedisSingleHelper for RedisClusterHelper or RedisSentinelHelper -- both satisfy the IRedisHelper interface the component validates against. See Redis Connection Alternatives.

Look up every default, binding key, and constant. Full DEFAULT_SERVER_OPTIONS, the binding key table, system events, default rooms, and the client state machine are in the API Reference.

See Also

Files: