Skip to main content

WS Api

Overview

OPRA's WebSocket API provides a decorator-driven layer for building real-time event handlers on top of adapters such as Socket.IO. You define controllers and event handlers once using TypeScript decorators; the adapter manages socket lifecycle, event routing, and serialization. Event arguments are automatically validated and decoded against their declared types before your handler is called, and response payloads are encoded and validated before they are sent back to the client as acknowledgements.

Event Receivedsocket → serverEvent Matchingcontroller · operationArgumentsdecode & validateHandlerasync methodAcknowledgementencode & emitoptionalerrorerroremit to socketerrorerroremit to socket

A WebSocket API in OPRA is built from two decorator types:

  • @WSController() — groups related event handlers under a single class.
  • @WSOperation() — defines a single event handler on a controller method. It declares the event name, argument types via @WsParam(), and an optional response type.

ApiDocument

import { ApiDocumentFactory } from '@opra/common';
import { MainController } from './api/main-controller.js';

const document = await ApiDocumentFactory.createDocument({
info: { title: 'Chat API', version: '1.0' },
api: {
transport: 'ws',
platform: 'Socketio',
name: 'ChatService',
controllers: [MainController],
},
});

WSController

Decorate a class with @WSController() to register it as a WebSocket controller.

import { WSController } from '@opra/common';

@WSController({
description: 'Chat room controller',
})
export class MainController { }

Use .UseType() to register types scoped to this controller and its operations:

@(WSController({ description: 'Chat room controller' })
.UseType(Room, RoomOptions))
export class MainController { }

Options

OptionTypeDescription
namestringRegistry name. Defaults to the class name without the Controller suffix.
descriptionstringHuman-readable description.

WSOperation

Decorate a controller method with @WSOperation() to define a WebSocket event handler. The event option sets the event name — if omitted, the method name is used.

import { WSController, WSOperation, WsParam } from '@opra/common';
import { SocketioContext } from '@opra/socketio';

@WSController({ description: 'Chat room controller' })
export class MainController {

@WSOperation({
event: 'create-room',
response: Room,
})
createRoom(
ctx: SocketioContext,
@WsParam() name: string,
@WsParam() options: RoomOptions,
) {
return this.roomService.createRoom(name, options);
}

@WSOperation({
event: 'list-rooms',
response: ArrayType(Room),
})
listRooms() {
return Array.from(this.roomService.rooms.values());
}
}

The event name also accepts a RegExp for pattern-based routing:

@WSOperation({ event: /^room:.*/ })
onRoomEvent(ctx: SocketioContext) { }

Options

OptionTypeDescription
eventstring | RegExpWebSocket event name or pattern. Defaults to the method name.
responsestring | TypeResponse type returned to the caller.
descriptionstringHuman-readable description.

WsParam

Use @WsParam() on method parameters to declare the argument types of an operation. Arguments are decoded in the order they appear in the method signature (excluding the context parameter).

import { WSController, WSOperation, WsParam } from '@opra/common';
import { SocketioContext } from '@opra/socketio';

@WSController()
export class GameController {

@WSOperation({ event: 'join-room', response: Boolean })
joinRoom(
ctx: SocketioContext, // context — not a WsParam
@WsParam() roomName: string,
@WsParam() options: JoinOptions,
) {
// roomName → first argument, options → second argument
}
}

OPRA infers the type from TypeScript's design metadata when possible. Pass an explicit type to override:

@WSOperation({ event: 'send-message' })
sendMessage(
ctx: SocketioContext,
@WsParam('uuid') senderId: string,
@WsParam(MessageDto) payload: MessageDto,
) { }

Options

OptionTypeDescription
typestring | TypeExplicit argument type. Inferred from TypeScript metadata if omitted.
requiredbooleanArgument must be provided by the client.

Response

Declare the response type in the response option of @WSOperation(). The response is returned to the caller as a WebSocket acknowledgement.

import { ArrayType, WSController, WSOperation, WsParam } from '@opra/common';
import { SocketioContext } from '@opra/socketio';

@WSController()
export class MainController {

// Respond with a single object
@WSOperation({ event: 'create-room', response: Room })
createRoom(
ctx: SocketioContext,
@WsParam() name: string,
) {
return this.roomService.createRoom(name);
}

// Respond with an array
@WSOperation({ event: 'list-rooms', response: ArrayType(Room) })
listRooms() {
return Array.from(this.roomService.rooms.values());
}

// Respond with a primitive
@WSOperation({ event: 'join-room', response: Boolean })
joinRoom(
ctx: SocketioContext,
@WsParam() roomName: string,
) {
return this.roomService.join(roomName, ctx.socket.data.user);
}
}

The return value of the handler method is automatically encoded using the declared response type and sent back to the client.