NestJS WebSockets

WebSockets create a persistent, two-way connection between the client and server. Unlike HTTP where the client always initiates a request, WebSockets let the server push data to the client at any time. Chat applications, live dashboards, real-time notifications, multiplayer games, and collaborative editing tools all use WebSockets because polling the server every few seconds is inefficient and slow.

HTTP vs WebSocket Communication

HTTP (Request-Response):
  Client → "give me new messages" → Server
  Server → "here are 0 messages"  → Client
  (repeated every few seconds — wasteful)

WebSocket (Persistent Connection):
  Client ←——————— Connection open ———————— Server
         ←— "New message from Alice!" ←—
         ←— "Bob is typing..."        ←—
         →— "User sends a message"    →—
  (server pushes data the instant something happens)

Setting Up WebSockets in NestJS

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

Creating a Gateway

A Gateway is the WebSocket equivalent of a Controller. It handles WebSocket connections and events:

import {
  WebSocketGateway, WebSocketServer,
  SubscribeMessage, MessageBody,
  ConnectedSocket, OnGatewayConnection, OnGatewayDisconnect
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway({
  cors: { origin: '*' },
  namespace: 'chat',   // optional: ws://localhost:3000/chat
})
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('sendMessage')
  handleMessage(
    @MessageBody() data: { room: string; message: string; sender: string },
    @ConnectedSocket() client: Socket,
  ) {
    // Broadcast to everyone in the room
    this.server.to(data.room).emit('receiveMessage', {
      sender: data.sender,
      message: data.message,
      timestamp: new Date().toISOString(),
    });
  }

  @SubscribeMessage('joinRoom')
  handleJoinRoom(
    @MessageBody() room: string,
    @ConnectedSocket() client: Socket,
  ) {
    client.join(room);
    client.emit('joinedRoom', room);
    this.server.to(room).emit('userJoined', { userId: client.id, room });
  }
}

Event Flow Diagram

Client A joins room "general":
  Client A → emit('joinRoom', 'general') → Gateway.handleJoinRoom()
  Gateway  → client.join('general')
  Gateway  → emit('userJoined', ...) to room 'general'

Client A sends a message:
  Client A → emit('sendMessage', { room, message, sender })
  Gateway.handleMessage() runs
  Gateway  → server.to('general').emit('receiveMessage', {...})
  Client A ← receives message
  Client B ← receives message (also in 'general' room)
  Client C ← does NOT receive (not in 'general' room)

Emitting from a Service

Sometimes you need to push data to clients from a service (not triggered by a WebSocket event) — for example, when a new database record is created via a REST endpoint:

@Injectable()
export class NotificationsService {
  constructor(
    @InjectModel(Notification.name)
    private notificationModel: Model<Notification>,
  ) {}

  async create(userId: string, message: string) {
    const notification = await this.notificationModel.create({ userId, message });

    // Push to the specific user's socket room
    this.chatGateway.server.to(`user-${userId}`).emit('notification', notification);

    return notification;
  }
}

Registering the Gateway as a Module

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

WebSocket Guards

Guards work on WebSocket gateways too. Apply them with @UseGuards() on the gateway class or individual message handlers:

@WebSocketGateway()
@UseGuards(WsJwtGuard)
export class ChatGateway {
  @SubscribeMessage('privateMessage')
  @UseGuards(WsJwtGuard)
  handlePrivateMessage(@MessageBody() data, @ConnectedSocket() client: Socket) {
    const user = client.data.user;   // set by the guard
    // ...
  }
}

Client-Side Connection (Browser)

import { io } from 'socket.io-client';

const socket = io('http://localhost:3000/chat');

socket.on('connect', () => {
  socket.emit('joinRoom', 'general');
});

socket.on('receiveMessage', (data) => {
  console.log(`${data.sender}: ${data.message}`);
});

// Send a message
socket.emit('sendMessage', {
  room: 'general',
  message: 'Hello everyone!',
  sender: 'Alice',
});

WebSockets in NestJS follow the same structural patterns as HTTP — gateways instead of controllers, @SubscribeMessage() instead of @Get(), the same guards and pipes. This consistency means the skills you build learning NestJS HTTP handling transfer directly to building real-time features with WebSockets.

Leave a Comment

Your email address will not be published. Required fields are marked *