How to Implement WebSocket Gateways in NestJS: A Complete Developer Guide
You implement WebSocket gateways in NestJS by creating a class decorated with @WebSocketGateway(), defining message handlers with @SubscribeMessage(), and optionally implementing lifecycle hooks like OnGatewayInit and OnGatewayConnection to manage server events and client connections.
NestJS provides first-class support for real-time communication through its WebSockets module, allowing developers to implement WebSocket gateways using a decorator-based approach similar to HTTP controllers. By leveraging decorators like @WebSocketGateway() and @SubscribeMessage(), you can create type-safe, scalable socket servers that integrate seamlessly with the NestJS dependency injection system. This guide examines the actual source code from the nestjs/nest repository to explain how gateways function internally and how to build production-ready implementations.
What Is a WebSocket Gateway in NestJS?
A WebSocket gateway in NestJS is a provider class that encapsulates a real-time server instance (such as Socket.IO) and defines methods to handle incoming socket messages. Unlike HTTP controllers that handle request-response cycles, gateways maintain persistent connections with clients and communicate through events.
The foundation of every gateway is the @WebSocketGateway() decorator, which marks the class for the NestJS runtime. According to the source code in packages/websockets/decorators/socket-gateway.decorator.ts, this decorator attaches three critical metadata keys to the class:
GATEWAY_METADATA– Flags the class as a WebSocket gatewayPORT_METADATA– Specifies the listening port (defaults to0)GATEWAY_OPTIONS– Stores optional configuration objects
These metadata keys are defined in packages/websockets/constants.ts and consumed by the runtime to configure the server instance.
// https://github.com/nestjs/nest/blob/master/packages/websockets/decorators/socket-gateway.decorator.ts#L20-L28
Reflect.defineMetadata(GATEWAY_METADATA, true, target);
Reflect.defineMetadata(PORT_METADATA, port, target);
Reflect.defineMetadata(GATEWAY_OPTIONS, opt, target);
Core Architecture and Metadata System
The runtime behavior of WebSocket gateways is orchestrated by the WebSocketsController class located in packages/websockets/web-sockets-controller.ts. This controller scans for gateway classes, extracts their metadata, and wires them to the underlying socket server.
Message Mapping Metadata
Individual methods within a gateway are bound to specific message patterns using the @SubscribeMessage() decorator. As implemented in packages/websockets/decorators/subscribe-message.decorator.ts, this decorator attaches:
MESSAGE_MAPPING_METADATA– Marks the method as a message handlerMESSAGE_METADATA– Stores the message pattern string
// https://github.com/nestjs/nest/blob/master/packages/websockets/decorators/subscribe-message.decorator.ts#L14-L16
Reflect.defineMetadata(MESSAGE_MAPPING_METADATA, true, descriptor.value);
Reflect.defineMetadata(MESSAGE_METADATA, message, descriptor.value);
Controller Initialization Process
When the NestJS application boots, the WebSocketsController executes the following steps for each discovered gateway:
- Metadata Retrieval – Calls
Reflect.getMetadatato extractGATEWAY_OPTIONSandPORT_METADATA(see lines 45-55 inweb-sockets-controller.ts) - Server Instantiation – Creates a socket server via
SocketServerProvideron the configured port - Handler Discovery – Scans the gateway instance for methods marked with
MESSAGE_MAPPING_METADATA - Event Wiring – Binds lifecycle hooks (
afterInit,handleConnection,handleDisconnect) through dedicated subscription methods:subscribeInitEvent,subscribeConnectionEvent, andsubscribeDisconnectEvent
// https://github.com/nestjs/nest/blob/master/packages/websockets/web-sockets-controller.ts#L45-L55
const options = Reflect.getMetadata(GATEWAY_OPTIONS, metatype) || {};
const port = Reflect.getMetadata(PORT_METADATA, metatype) || 0;
Message Execution Flow
When a client emits a message matching a registered pattern, the controller retrieves the appropriate handler via subscribeMessages. The handler is bound to the client context and executed, with the return value automatically wrapped into an Observable to support promises, streams, or plain values.
// https://github.com/nestjs/nest/blob/master/packages/websockets/web-sockets-controller.ts#L85-L87
const handlers = subscribersMap.map(({ callback, message, isAckHandledManually }) => ({
message,
callback: callback.bind(instance, client),
isAckHandledManually,
}));
Implementing a Basic WebSocket Gateway
To implement a simple gateway that responds to client messages, create a class decorated with @WebSocketGateway() and define handler methods using @SubscribeMessage().
// src/app.gateway.ts
import {
WebSocketGateway,
SubscribeMessage,
MessageBody,
} from '@nestjs/websockets';
@WebSocketGateway(3001) // Optional: specify port
export class AppGateway {
@SubscribeMessage('ping')
handlePing(@MessageBody() data: any) {
return { event: 'pong', data };
}
}
The @WebSocketGateway(3001) decorator registers this class to listen on port 3001, storing this value in PORT_METADATA. The handlePing method binds to the "ping" message pattern through MESSAGE_METADATA, allowing the WebSocketsController to route incoming "ping" events to this specific handler.
Handling Lifecycle Events in Your Gateway
Production gateways often require logic to execute when clients connect, disconnect, or when the server initializes. NestJS provides three optional interfaces: OnGatewayInit, OnGatewayConnection, and OnGatewayDisconnect.
When a gateway implements these interfaces, the WebSocketsController automatically subscribes to the corresponding server events through its internal subscribeInitEvent, subscribeConnectionEvent, and subscribeDisconnectEvent methods.
// src/chat.gateway.ts
import {
WebSocketGateway,
SubscribeMessage,
ConnectedSocket,
MessageBody,
OnGatewayInit,
OnGatewayConnection,
OnGatewayDisconnect,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway()
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
private server: Server;
afterInit(server: Server) {
this.server = server;
console.log('WebSocket server initialized');
}
handleConnection(client: Socket) {
console.log(`Client connected: ${client.id}`);
}
handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}
@SubscribeMessage('message')
handleMessage(
@MessageBody() payload: string,
@ConnectedSocket() client: Socket,
) {
// Broadcast to all connected clients
this.server.emit('message', payload);
}
}
The afterInit method receives the server instance immediately after creation, while handleConnection and handleDisconnect receive the specific Socket client object for connection management.
Advanced Patterns: Interceptors, Filters, and Error Handling
NestJS WebSocket gateways support the same interceptor and exception filter patterns as HTTP controllers, enabling cross-cutting concerns like logging, transformation, and error handling.
The following implementation from integration/websockets/src/app.gateway.ts demonstrates a production-ready gateway with error handling using WsException, request interceptors, and exception filters:
// src/app.gateway.ts
import {
WebSocketGateway,
SubscribeMessage,
MessageBody,
UseFilters,
UseInterceptors,
ConnectedSocket,
} from '@nestjs/websockets';
import { WsException } from '@nestjs/websockets';
import { RequestInterceptor } from './request.interceptor';
import { RequestFilter } from './request.filter';
import { throwError } from 'rxjs';
@WebSocketGateway(8080)
export class ApplicationGateway {
@SubscribeMessage('push')
onPush(@MessageBody() data: any) {
return { event: 'pop', data };
}
@UseInterceptors(RequestInterceptor)
@SubscribeMessage('getClient')
getPathCalled(@ConnectedSocket() client: any, @MessageBody() data: any) {
return { event: 'popClient', data: { ...data, path: client.pattern } };
}
@UseFilters(RequestFilter)
@SubscribeMessage('getClientWithError')
getPathCalledWithError() {
return throwError(() => new WsException('This is an error'));
}
}
Key implementation details:
@UseInterceptors(RequestInterceptor)– Applies interceptors to transform requests or responses for specific message handlers@UseFilters(RequestFilter)– Catches exceptions thrown within the handler using theWsExceptionclassthrowErrorwithWsException– Returns an RxJS stream that emits an error, which the controller wraps and handles appropriately
Summary
Implementing WebSocket gateways in NestJS involves understanding the framework's metadata-driven architecture and lifecycle management:
- Decorator Metadata – The
@WebSocketGateway()decorator inpackages/websockets/decorators/socket-gateway.decorator.tsstoresGATEWAY_METADATA,PORT_METADATA, andGATEWAY_OPTIONSto configure the server instance - Message Mapping – The
@SubscribeMessage()decorator inpackages/websockets/decorators/subscribe-message.decorator.tsattachesMESSAGE_MAPPING_METADATAandMESSAGE_METADATAto bind methods to specific socket events - Runtime Wiring – The
WebSocketsControllerinpackages/websockets/web-sockets-controller.tsorchestrates server creation, handler discovery, and lifecycle event subscription through methods likeconnectGatewayToServerandsubscribeToServerEvents - Lifecycle Hooks – Implement
OnGatewayInit,OnGatewayConnection, andOnGatewayDisconnectto hook into server initialization and client connection events - Advanced Features – Apply interceptors with
@UseInterceptors()and handle errors usingWsExceptioncombined with@UseFilters()for robust message processing
Frequently Asked Questions
What port does a NestJS WebSocket gateway use by default?
By default, a NestJS WebSocket gateway listens on port 0, which typically assigns a random available port. The WebSocketsController retrieves this value from PORT_METADATA (defined in packages/websockets/decorators/socket-gateway.decorator.ts), defaulting to 0 if no port is specified in the @WebSocketGateway() decorator options.
How does NestJS map incoming socket messages to gateway methods?
NestJS maps messages through metadata scanning. When a message arrives, the WebSocketsController locates handlers marked with MESSAGE_MAPPING_METADATA (set by @SubscribeMessage() in packages/websockets/decorators/subscribe-message.decorator.ts) and matches the incoming pattern against MESSAGE_METADATA. The controller then binds the handler to the client instance and executes it, wrapping the result in an Observable to support asynchronous responses.
What lifecycle hooks are available for WebSocket gateways in NestJS?
NestJS provides three primary lifecycle interfaces: OnGatewayInit (triggered via subscribeInitEvent), OnGatewayConnection (triggered via subscribeConnectionEvent), and OnGatewayDisconnect (triggered via subscribeDisconnectEvent). These are implemented in packages/websockets/web-sockets-controller.ts and allow you to execute logic when the server starts, when clients connect, and when clients disconnect respectively.
How can I handle errors in WebSocket message handlers?
You can handle errors by throwing WsException from the @nestjs/websockets package and applying @UseFilters() with a custom exception filter to the handler method. As shown in the integration/websockets/src/app.gateway.ts example, returning throwError(() => new WsException('error message')) creates an observable error stream that the controller processes through the registered filter, allowing you to format error responses consistently.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →