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.
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.
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
| Option | Type | Description |
|---|---|---|
name | string | Registry name. Defaults to the class name without the Controller suffix. |
description | string | Human-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
| Option | Type | Description |
|---|---|---|
event | string | RegExp | WebSocket event name or pattern. Defaults to the method name. |
response | string | Type | Response type returned to the caller. |
description | string | Human-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
| Option | Type | Description |
|---|---|---|
type | string | Type | Explicit argument type. Inferred from TypeScript metadata if omitted. |
required | boolean | Argument 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.