NestJS — Enterprise Node.js

WebSockets & Gateways

16 min Lesson 35 of 48

WebSockets & Gateways

HTTP is a request/response protocol — the client always initiates, and the connection closes after each exchange. WebSockets flip that model: a single persistent connection lets either side push messages at any time, enabling real-time features such as chat, live dashboards, notifications, and collaborative editing.

NestJS wraps WebSocket libraries (Socket.IO by default, or the native ws adapter) behind a consistent, decorator-driven API called Gateways. A gateway looks and behaves almost exactly like a controller — you decorate a class, annotate handler methods, and inject services — so existing NestJS patterns transfer directly.

Setting up

npm install @nestjs/websockets @nestjs/platform-socket.io socket.io
Adapter choice. @nestjs/platform-socket.io uses Socket.IO (with rooms, namespaces, and automatic reconnection). For a leaner build without those extras you can swap to @nestjs/platform-ws which uses the native ws package; the gateway decorators work with both.

Creating a gateway

A gateway is a class decorated with @WebSocketGateway(). Optionally pass a port and CORS options. Use @WebSocketServer() to inject the underlying server instance, and @SubscribeMessage() to handle named events from clients:

import { WebSocketGateway, WebSocketServer, SubscribeMessage, MessageBody, ConnectedSocket, OnGatewayConnection, OnGatewayDisconnect, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; @WebSocketGateway({ cors: { origin: '*' } }) export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; handleConnection(client: Socket) { console.log(`Client connected: ${client.id}`); } handleDisconnect(client: Socket) { console.log(`Client disconnected: ${client.id}`); } @SubscribeMessage('message') handleMessage( @MessageBody() data: string, @ConnectedSocket() client: Socket, ): void { // Broadcast the message to every connected client this.server.emit('message', { from: client.id, text: data }); } }
  • @WebSocketGateway() — marks the class as a WebSocket handler. Without arguments it attaches to the same port as the HTTP server.
  • @WebSocketServer() — injects the Server instance so you can emit events globally.
  • @SubscribeMessage('event') — maps incoming Socket.IO event names to handler methods.
  • @MessageBody() — extracts the payload from the incoming event (equivalent to @Body() in HTTP).
  • @ConnectedSocket() — injects the individual client socket.

Rooms and targeted broadcasting

Socket.IO rooms let you group sockets and send messages only to members of a specific group — perfect for per-channel chat, game lobbies, or per-document collaboration:

@SubscribeMessage('joinRoom') handleJoin( @MessageBody() room: string, @ConnectedSocket() client: Socket, ): void { client.join(room); // Notify everyone already in the room this.server.to(room).emit('userJoined', { id: client.id, room }); } @SubscribeMessage('roomMessage') handleRoomMessage( @MessageBody() payload: { room: string; text: string }, @ConnectedSocket() client: Socket, ): void { // Send only to sockets in the target room this.server.to(payload.room).emit('roomMessage', { from: client.id, text: payload.text, }); }
Emit back to the sender only with client.emit(). Emit to everyone except the sender with client.broadcast.emit(). Emit to a specific room with this.server.to(room).emit(). Knowing which variant to use avoids duplicate messages.

Namespaces

Namespaces provide a logical channel separation at the connection level — useful when one server hosts unrelated real-time features (e.g., /chat and /notifications). Specify a namespace in the decorator:

@WebSocketGateway({ namespace: '/chat', cors: { origin: '*' } }) export class ChatGateway { /* ... */ } @WebSocketGateway({ namespace: '/notifications', cors: { origin: '*' } }) export class NotificationGateway { /* ... */ }

Registering the gateway

Gateways are plain providers — declare them in a module's providers array exactly like services:

@Module({ providers: [ChatGateway, ChatService], }) export class ChatModule {}
CORS in production. The example above uses origin: '*' for development. In production, restrict the origin to your frontend domain to prevent unauthorised WebSocket connections.

Summary

NestJS gateways bring the same decorator-driven, dependency-injectable style to WebSockets. @WebSocketGateway() declares the handler class, @SubscribeMessage() routes named events to methods, @WebSocketServer() exposes the underlying Socket.IO Server for global or targeted emits, and Socket.IO rooms allow fine-grained broadcasting to subsets of connected clients. Namespaces let you run multiple logical channels on one server. Because gateways are providers, you inject services into them exactly as you would in a controller.