Skip to main content

Socket.IO Module

The @opra/nestjs-socketio package integrates the OPRA Socket.IO adapter into a NestJS application. It discovers your OPRA WebSocket controllers automatically from the NestJS provider tree, wires up the Socket.IO server lifecycle through NestJS hooks, and resolves class-based interceptors through the NestJS DI container — all transparently.


Installation

npm install @opra/nestjs-socketio @opra/socketio @opra/common

Setup

Import OpraSocketioModule in your root application module:

import { Module } from '@nestjs/common';
import { OpraSocketioModule } from '@opra/nestjs-socketio';
import { ChatController } from './chat/chat.controller.js';
import { ChatService } from './chat/chat.service.js';
import * as models from './models/models.js';

@Module({
imports: [
OpraSocketioModule.forRoot({
imports: [DatabaseModule],
providers: [ChatController, ChatService],
name: 'ChatApi',
info: { title: 'Chat API', version: '1.0' },
types: [...Object.values(models)],
schemaIsPublic: true,
}),
],
})
export class AppModule {}

Unlike the HTTP module, you do not pass a controllers list — OPRA discovers all providers decorated with @WSController automatically from the NestJS module context.

Options

OptionTypeDescription
namestringAPI name.
infoobjectDocument metadata — title, version, description.
typesany[]Data types to register (decorated classes, EnumType results).
referencesRecord<string, ReferenceThunk>Namespaced references to other ApiDocument instances or async thunks.
portnumberPort for the Socket.IO server. If omitted or equal to the HTTP server port, Socket.IO is attached to the existing HTTP server. If different, a separate server is started.
serverOptionsPartial<socketio.ServerOptions>Options passed directly to the Socket.IO server (e.g. path, cors, transports).
scopestringValidation scope applied to every incoming event and outgoing response.
interceptors(InterceptorFunction | IWSInterceptor | Type<IWSInterceptor>)[]Interceptor chain executed on every event. Class types are resolved through the NestJS DI container.
importsany[]NestJS modules to import into the OPRA module context.
providersProvider[]NestJS providers available for injection inside controllers.
exportsany[]Providers to export from the OPRA module to the rest of the application.
globalbooleanRegister the module as a NestJS global module.

Async configuration

Use forRootAsync() when info, types, or other options depend on injected services:

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { OpraSocketioModule } from '@opra/nestjs-socketio';
import * as models from './models/models.js';

@Module({
imports: [
ConfigModule.forRoot(),
OpraSocketioModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
providers: [ChatController, ChatService],
useFactory: (config: ConfigService) => ({
name: 'ChatApi',
info: {
title: 'Chat API',
version: config.get('API_VERSION'),
},
types: [...Object.values(models)],
port: config.get('WS_PORT'),
}),
}),
],
})
export class AppModule {}

How it works

OpraSocketioModule scans all NestJS providers in its module context for classes decorated with @WSController and builds the ApiDocument from them automatically. No explicit controller list is required.

Once the document is ready, the module creates a SocketioAdapter and attaches it to the application during onApplicationBootstrap:

  • If port is omitted or matches the HTTP server port, Socket.IO is attached to the existing HTTP server — no extra port needed.
  • If port is set to a different value, a separate HTTP server is started and Socket.IO listens on it.

The adapter is cleanly shut down during onApplicationShutdown.


Dependency injection in controllers

OPRA WebSocket controllers are registered as NestJS providers, so you can inject services directly into them:

import { WSController, WSOperation } from '@opra/common';
import { SocketioContext } from '@opra/socketio';
import { Injectable } from '@nestjs/common';
import { ChatService } from './chat.service.js';

@Injectable()
@WSController()
export class ChatController {
constructor(private readonly service: ChatService) {}

@WSOperation({ event: 'send-message' })
async sendMessage(ctx: SocketioContext, payload: MessageDto) {
return this.service.save(payload);
}
}

Register the controller as a NestJS provider — either in providers of the module options or in any imported module:

OpraSocketioModule.forRoot({
providers: [ChatController, ChatService],
// ...
})

Guards and interceptors

NestJS guards and interceptors work normally on OPRA WebSocket controllers. Apply them at the class or method level:

import { UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard.js';

@UseGuards(AuthGuard)
@WSController()
export class ChatController { ... }

OPRA-level interceptors (the interceptors option) run on every incoming event before the operation handler. Class-based interceptors are resolved through the NestJS DI container, so they can have injected dependencies:

import { IWSInterceptor, SocketioContext } from '@opra/socketio';
import { Injectable } from '@nestjs/common';
import { LogService } from './log.service.js';

@Injectable()
class LoggingInterceptor implements IWSInterceptor {
constructor(private readonly log: LogService) {}

async intercept(ctx: SocketioContext, next: () => Promise<any>) {
this.log.info(`Event: ${ctx.event} socket=${ctx.socket.id}`);
await next();
}
}

OpraSocketioModule.forRoot({
providers: [ChatController, ChatService, LogService, LoggingInterceptor],
interceptors: [LoggingInterceptor], // resolved via DI
// ...
})

Separate Socket.IO port

By default Socket.IO shares the same port as the NestJS HTTP server. To run Socket.IO on a dedicated port, set the port option:

OpraSocketioModule.forRoot({
port: 3001,
serverOptions: {
cors: { origin: 'https://myapp.com' },
},
// ...
})

The NestJS HTTP server continues to serve REST routes on its own port; Socket.IO listens independently on port 3001.