@WebSocketRoute
export function WebSocketRoute(path?: string): MethodDecorator;
export namespace WebSocketRoute {
export function Acceptor(): ParameterDecorator;
export function Driver(): ParameterDecorator;
export function Header(): ParameterDecorator;
export function Param(field: string): ParameterDecorator;
export function Query(): ParameterDecorator;
}Type-safe WebSocket routes for NestJS. A @WebSocketRoute() method exposes an RPC endpoint that the client calls like a remote object β function calls in both directions, no message handlers, no string commands.
Built on TGridΒ , which handles the RPC layer. @WebSocketRoute is the NestJS adapter β it tells the framework how to mount the route and metadata for @nestia/sdk so the generated client gets typed driver.plus(4, 2) calls.
Vanilla NestJS @WebSocketGateway works fine for event-style WebSockets (chat messages, pub/sub). Reach for @WebSocketRoute when you want RPC β clients calling typed functions on the server (and the server calling typed functions on the client) with full IDE autocomplete on both sides.
Setup
Upgrade your NestJS application to accept WebSocket connections:
import { WebSocketAdaptor } from "@nestia/core";
import { INestApplication } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./AppModule";
const main = async () => {
const app: INestApplication = await NestFactory.create(AppModule);
await WebSocketAdaptor.upgrade(app);
await app.listen(3000);
};
main().catch(console.error);Without WebSocketAdaptor.upgrade(app), methods decorated with @WebSocketRoute() never receive connections.
Basic usage β a calculator
A typical pattern: client connects, server hands the client a typed provider, client calls methods on it, server pushes events back through a listener.
Controller
import { WebSocketRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
import { Driver, WebSocketAcceptor } from "tgrid";
import { ICalculator } from "./api/structures/ICalculator";
import { IListener } from "./api/structures/IListener";
import { Calculator } from "./providers/Calculator";
@Controller("calculate")
export class CalculateController {
/**
* Start a simple calculator session.
*/
@WebSocketRoute("start")
public async start(
@WebSocketRoute.Acceptor()
acceptor: WebSocketAcceptor<any, ICalculator, IListener>,
@WebSocketRoute.Driver() driver: Driver<IListener>,
): Promise<void> {
await acceptor.accept(new Calculator(driver));
}
}Whatβs happening:
- The client connects to
/calculate/startover WebSocket. - The server accepts the connection and exposes a
Calculatorinstance. - The client receives a
Driver<ICalculator>β every method on it returns aPromise<R>because the call hops the network. - Each operation also pushes an event back to the client via the
IListenerdriver.
The SDK function api.functional.calculate.start(...) is generated automatically when you run npx nestia sdk. The types stay aligned across the wire.
Parameter decorators
Inside a @WebSocketRoute(path) method, the same way you destructure HTTP requests, you can decorate WebSocket parameters:
| Decorator | What it gives you |
|---|---|
@WebSocketRoute.Acceptor() | The WebSocketAcceptor<Header, Provider, Listener> β required to accept the connection. |
@WebSocketRoute.Driver() | The Driver<Listener> β a typed handle to the clientβs provider. |
@WebSocketRoute.Header() | Typed header object the client sent at handshake. |
@WebSocketRoute.Param("name") | Path parameter (/calc/:id/advanced β "id"). Same coercion as @TypedParam. |
@WebSocketRoute.Query() | Typed query-string DTO. Same parsing as @TypedQuery. |
@WebSocketRoute(":id/advanced")
public async advanced(
@WebSocketRoute.Param("id") id: string & tags.Format<"uuid">,
@WebSocketRoute.Header() header: Partial<IHeader> | undefined,
@WebSocketRoute.Query() memo: IMemo,
@WebSocketRoute.Acceptor()
acceptor: WebSocketAcceptor<IHeader, IAdvCalculator, IListener>,
@WebSocketRoute.Driver() driver: Driver<IListener>,
): Promise<void> { ... }Path / header / query are validated against their declared types β invalid handshakes are rejected before your handler runs.
The acceptor lifecycle
WebSocketAcceptor<Header, Provider, Listener> is a state machine with three useful methods:
accept(provider)β agree to the handshake, exposeproviderto the client.reject(status, reason)β refuse the handshake (auth failure, etc.).close(code?, reason?)β terminate the connection after acceptance.
The convention: validate the handshake first, then accept. If anything is wrong, reject:
@WebSocketRoute("auth")
public async auth(
@WebSocketRoute.Header() header: { token?: string },
@WebSocketRoute.Acceptor()
acceptor: WebSocketAcceptor<{}, ISession, ISessionListener>,
): Promise<void> {
const session = await this.tokens.verify(header.token);
if (!session) return acceptor.reject(1008, "Invalid token");
await acceptor.accept(this.sessions.create(session));
}Caveats
- Acceptor parameter is required. Without an
@WebSocketRoute.Acceptor()parameter, the route never accepts the handshake and SDK generation skips it. - Driver methods are async on the wire. A server-side
plus(x: number, y: number): numberbecomesplus(x, y): Promise<number>in the clientβsDriver<T>. - One driver per direction. Server-to-client calls go through
Driver<Listener>; client-to-server calls go through theProviderinstance you pass toacceptor.accept(). Theyβre independent. - No SDK without TGrid. The client SDK pulls in
tgridas a runtime dependency.
References
- TGrid DocsΒ β RPC concepts.
- TGrid + NestJS exampleΒ β the calculator pattern in full.
- PlaygroundΒ β same example, runs in the browser.
See also
- TypedRoute β HTTP-side route decorators.
- SDK β how WebSocket routes appear in the generated client.