WebSockets With Socket.IO

Push Events to Connected Clients in Real Time

WebSockets With Socket.IO

Build a Gateway — a class that handles WebSocket events. It's the WS analog of a controller, with all the DI you're used to.

4 min read Level 3/5 #nestjs#websockets#realtime
What you'll learn
  • Install @nestjs/websockets and platform-socket.io
  • Build a Gateway with @SubscribeMessage handlers
  • Broadcast with server.emit

WebSockets keep a TCP connection open between client and server so either side can push messages whenever it wants. Nest’s @nestjs/websockets package wraps Socket.IO (or plain ws) into a familiar class-based API: a Gateway is what a Controller is to HTTP.

Install

npm i @nestjs/websockets @nestjs/platform-socket.io socket.io

Build a Gateway

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('connected', client.id);
  }

  handleDisconnect(client: Socket) {
    console.log('disconnected', client.id);
  }

  @SubscribeMessage('message')
  onMessage(
    @MessageBody() data: { text: string },
    @ConnectedSocket() client: Socket,
  ) {
    this.server.emit('message', { id: client.id, text: data.text });
  }
}

A few things to notice:

  • @WebSocketServer() injects the Socket.IO Server instance — that’s how you broadcast.
  • @SubscribeMessage('message') is the WebSocket equivalent of @Post() — it handles incoming events named 'message'.
  • Inside the handler, you have full DI just like a controller method.

Register the Gateway

Gateways are providers. Add them to a module’s providers array — no controllers needed.

@Module({ providers: [ChatGateway] })
export class ChatModule {}

Rooms for Targeted Broadcasts

server.emit sends to everyone. To send to a subset, use rooms:

@SubscribeMessage('joinRoom')
join(@MessageBody() room: string, @ConnectedSocket() client: Socket) {
  client.join(room);
}

@SubscribeMessage('roomMessage')
roomMessage(@MessageBody() { room, text }: { room: string; text: string }) {
  this.server.to(room).emit('roomMessage', text);
}

Rooms are perfect for chat threads, document collaborators, or any “everyone watching X” use case.

Auth Over WebSockets

The browser doesn’t send custom headers on the initial WS handshake, so the usual trick is to pass the token as a query parameter or in the auth payload that Socket.IO supports:

// client
io('/chat', { auth: { token } });

// server, in handleConnection
const token = client.handshake.auth.token;
// verify with JwtService, attach user to client.data.user

You can wrap this in a custom WsAuthGuard and apply it with @UseGuards() on each @SubscribeMessage handler.

GraphQL — Code-First →