Building a Chat App with Gateway, Rooms, and Events in NestJS

Real-time chat applications are a perfect use case for WebSockets. In this module, we’ll build a basic chat app using NestJS WebSocket Gateways, Socket.IO rooms, and custom events. You’ll learn how to structure a real-time communication system with NestJS that supports multiple users and chat rooms.


Table of Contents

  1. Overview of Real-Time Chat Features
  2. Setting Up the WebSocket Gateway
  3. Creating Chat Rooms Using Socket.IO
  4. Managing Users and Broadcasting Events
  5. Frontend Socket.IO Client
  6. Handling Room Events and Messages
  7. Bonus: Persisting Messages (Optional)
  8. Conclusion

Overview of Real-Time Chat Features

In our chat app, users can:

  • Connect via WebSocket
  • Join specific rooms (like channels)
  • Send messages to others in the same room
  • Receive messages in real-time
  • Optionally persist messages in a database

We’ll use:

  • @nestjs/websockets for gateway setup
  • socket.io for client-server real-time communication
  • Simple in-memory logic for rooms and broadcasting

Setting Up the WebSocket Gateway

Install the required packages if you haven’t already:

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

Create a WebSocket Gateway:

tsCopyEdit// chat.gateway.ts
import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  MessageBody,
  ConnectedSocket,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway({ cors: true })
export class ChatGateway {
  @WebSocketServer()
  server: Server;

  @SubscribeMessage('joinRoom')
  handleJoinRoom(
    @MessageBody() room: string,
    @ConnectedSocket() client: Socket,
  ) {
    client.join(room);
    client.emit('joinedRoom', `Joined room: ${room}`);
  }

  @SubscribeMessage('sendMessage')
  handleMessage(
    @MessageBody() payload: { room: string; message: string },
    @ConnectedSocket() client: Socket,
  ) {
    const { room, message } = payload;
    this.server.to(room).emit('receiveMessage', {
      user: client.id,
      message,
    });
  }
}

Creating Chat Rooms Using Socket.IO

With Socket.IO, creating rooms is simple. You use client.join(roomName) to add a client to a room. Rooms can be dynamic and identified by chat group names, user IDs, or custom IDs.

tsCopyEditclient.join('dev-room'); // Adds client to the 'dev-room'

Messages can then be scoped to a room:

tsCopyEditthis.server.to('dev-room').emit('receiveMessage', payload);

Managing Users and Broadcasting Events

To enhance functionality, you can track connected users and rooms using a simple map or a database.

Example:

tsCopyEditconst activeUsers: Record<string, string> = {}; // clientId -> username

@SubscribeMessage('register')
handleRegister(
  @MessageBody() username: string,
  @ConnectedSocket() client: Socket,
) {
  activeUsers[client.id] = username;
  client.emit('registered', `Welcome, ${username}`);
}

When a user sends a message:

tsCopyEditthis.server.to(room).emit('receiveMessage', {
  user: activeUsers[client.id] || client.id,
  message,
});

Frontend Socket.IO Client

htmlCopyEdit<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script>
  const socket = io('http://localhost:3000');

  socket.emit('joinRoom', 'room1');

  socket.on('joinedRoom', (msg) => {
    console.log(msg);
  });

  socket.emit('sendMessage', {
    room: 'room1',
    message: 'Hello Room!',
  });

  socket.on('receiveMessage', (msg) => {
    console.log(`[${msg.user}]: ${msg.message}`);
  });
</script>

Handling Room Events and Messages

You can extend your gateway to handle:

  • User typing indicators
  • Message read receipts
  • User disconnection announcements
  • Notifications for new users

Example: Notify room on user join

tsCopyEdithandleJoinRoom(room: string, client: Socket) {
  client.join(room);
  this.server.to(room).emit('userJoined', {
    user: client.id,
    room,
  });
}

Bonus: Persisting Messages (Optional)

For production apps, persist messages using a database:

  1. Create a Message entity/model
  2. Use a MessageService to save chat data
  3. Call the service in handleMessage
tsCopyEditawait this.messageService.save({
  room,
  userId: client.id,
  message,
  timestamp: new Date(),
});

Conclusion

You’ve now built a simple but powerful chat app using NestJS Gateways and Socket.IO. You’ve learned how to:

  • Create and manage chat rooms
  • Handle real-time message delivery
  • Build a frontend that interacts with the backend
  • Optionally persist chat messages