How to Implement Real-Time Live Visitors Count Using Socket.io in Nest.js

ยท

5 min read

Last time we implement a secure socket.io connection in nest js using jwt and password strategy authentication process this time we will dive into implementing how we can implement a real time live visitors in any web pages when user visit the web pages. These type of use cases is widly applicable in application like news portal site, blog portal sites or any other kinds of e-commerce ๐Ÿ˜…๐Ÿ˜… such that you can use where you like. Okey lets dive in shall we!

For this you must need to have a basic understanfing of MAP in js no worries if you don't know i'll give a simple example where you can have a basic understanding of how MAP works and how does it store the data.

const map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');

console.log(map.has('key1')); // Output: true
console.log(map.has('key3')); // Output: false

Since we are using TS we need to declare and initialize two private memebers variable with it's type.

  private connectedClients = new Map<string, Set<Socket>>();
  private blogClients = new Map<string, number>();

After successfully declaring and initializing the private instance variable the mail logics cames here. First review the code and i'll have you a basic understanding of how work flows.

 @SubscribeMessage('blogLiveCounts')
  handleBlogLiveCounts(
    @ConnectedSocket() client: Socket,
    @MessageBody() message: LiveBlog,
  ) {
    const parsedMessage: LiveBlog = JSON.parse(`${message}`);
    console.log('๐Ÿš€ ~ SocketGateway ~ jsonStringify:', parsedMessage.blog_id); //๐Ÿš€ ~ SocketGateway ~ jsonStringify: 2

    if (!this.blogClients.has(client.id)) {
      console.log('๐Ÿš€ ~ SocketGateway ~ client.id, :', client.id); //๐Ÿš€ ~ SocketGateway ~ client.id, : 7Fu4_qBToYPCBrlkAAAB
      this.blogClients.set(client.id, parsedMessage.blog_id);
    }
    console.log('๐Ÿš€ ~ SocketGateway ~ this.blogClients:', this.blogClients); // example ๐Ÿš€ ~ SocketGateway ~ this.blogClients: Map(1) { '7Fu4_qBToYPCBrlkAAAB' => 2 }

    // //getting all the values stored in blogClients
    const allBlogClientsValues = Array.from(this.blogClients.values());
    console.log(
      '๐Ÿš€ ~ SocketGateway ~ allBlogClientsValues:',
      allBlogClientsValues,
    ); ///example :- ๐Ÿš€ ~ SocketGateway ~ allBlogClientsValues: [ 2 ]

    const filteredValues = allBlogClientsValues.filter((blog_id) => {
      return blog_id === parsedMessage.blog_id;
    });
    console.log(
      '๐Ÿš€ ~ SocketGateway ~ filteredValues ~ filteredValues:',
      filteredValues,
      filteredValues.length,
    ); //๐Ÿš€ ~ SocketGateway ~ filteredValues ~ filteredValues: [ 2 ] 1

    this.server.emit('liveCountsSingleBlog', filteredValues.length);
  }

So, in this piece of code, I've implemented a method called handleBlogLiveCounts within my SocketGateway class. This method is triggered whenever an event named 'blogLiveCounts' is received.

First, I extract the message body, which presumably contains information about a live blog, such as its ID, title, and content. I parse this message into a LiveBlog object.

Then, I extract the blog_id from the parsed message and log it to the console. This helps me understand which blog the message is referring to.

Next, I check if the blogClients map doesn't already have the client's ID stored. This check ensures that I'm not duplicating entries for the same client. If the client's ID isn't already in the map, I add it along with the blog_id to the blogClients map.

After adding the client's ID and blog_id to the map, I log the entire blogClients map to the console. This helps me keep track of which clients are currently connected and which blogs they're viewing.

Then, I retrieve all the blog_id values stored in the blogClients map. This step is crucial because it allows me to determine how many clients are currently viewing each blog.

Once I have all the blog_id values, I filter them to find those that match the blog_id from the parsed message. This filtering process helps me identify the clients that are viewing the same blog as the one mentioned in the message.

Finally, I emit an event named 'liveCountsSingleBlog' to the server. I pass the count of filtered values (i.e., the number of clients viewing the same blog) as the payload of this event. This allows other parts of the application to receive and act upon this information in real-time.

Below is the entire work of how we can implement the Live visitors count in any web pages using socket and Nest.js

import { UseGuards } from '@nestjs/common';
import {
  ConnectedSocket,
  MessageBody,
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { JwtSocketGuard } from 'src/auth/guard/socket.guard';

interface LiveBlog {
  blog_id: number;
  user_id: number;
}

@UseGuards(JwtSocketGuard)
@WebSocketGateway(5000)
export class SocketGateway {
  @WebSocketServer() server: Server;

  private connectedClients = new Map<string, Set<Socket>>();
  private blogClients = new Map<string, number>();

  handleConnection(@ConnectedSocket() client: Socket) {
    // Initialize the client's private room
    console.log(
      `๐Ÿš€ ~ SocketGateway ~ handleConnection ~ client.id: ${client.id} is connected`,
    );
    this.connectedClients.set(client.id, new Set());
  }

  @SubscribeMessage('blogLiveCounts')
  handleBlogLiveCounts(
    @ConnectedSocket() client: Socket,
    @MessageBody() message: LiveBlog,
  ) {
    const parsedMessage: LiveBlog = JSON.parse(`${message}`);
    console.log('๐Ÿš€ ~ SocketGateway ~ jsonStringify:', parsedMessage.blog_id); //๐Ÿš€ ~ SocketGateway ~ jsonStringify: 2

    if (!this.blogClients.has(client.id)) {
      console.log('๐Ÿš€ ~ SocketGateway ~ client.id, :', client.id); //๐Ÿš€ ~ SocketGateway ~ client.id, : 7Fu4_qBToYPCBrlkAAAB
      this.blogClients.set(client.id, parsedMessage.blog_id);
    }
    console.log('๐Ÿš€ ~ SocketGateway ~ this.blogClients:', this.blogClients); // example ๐Ÿš€ ~ SocketGateway ~ this.blogClients: Map(1) { '7Fu4_qBToYPCBrlkAAAB' => 2 }

    // //getting all the values stored in blogClients
    const allBlogClientsValues = Array.from(this.blogClients.values());
    console.log(
      '๐Ÿš€ ~ SocketGateway ~ allBlogClientsValues:',
      allBlogClientsValues,
    ); ///example :- ๐Ÿš€ ~ SocketGateway ~ allBlogClientsValues: [ 2 ]

    const filteredValues = allBlogClientsValues.filter((blog_id) => {
      return blog_id === parsedMessage.blog_id;
    });
    console.log(
      '๐Ÿš€ ~ SocketGateway ~ filteredValues ~ filteredValues:',
      filteredValues,
      filteredValues.length,
    ); //๐Ÿš€ ~ SocketGateway ~ filteredValues ~ filteredValues: [ 2 ] 1

    this.server.emit('liveCountsSingleBlog', filteredValues.length);
  }


  handleDisconnect(@ConnectedSocket() client: Socket) {
    this.connectedClients.delete(client.id);
    this.blogClients.delete(client.id);
    const countConnectedClient = Array.from(
      this.connectedClients.values(),
    ).length;

    const countConnectedBlogClient = Array.from(
      this.blogClients.values(),
    ).length;

  }
}

Remember to delete the client.id as we used client.id which implies the id generated by socket for a client when user is connected to socket. Since we are setting up that id as a key in Map when delete client.id the live visitors count also decreased.

In my WebSocketGateway implementation using NestJS, I've ensured that when a client disconnects from the WebSocket, I clean up their associated resources by removing their client.id from both the connectedClients and blogClients maps in the handleDisconnect method. This step is crucial to maintain the integrity of our system and prevent any resource leaks.

Regarding the live visitor count, I've designed it to be based on the number of clients connected to a specific blog. To achieve this, I store the blog_id associated with each client's client.id in the blogClients map. Consequently, when a client disconnects, I remove their entry from the blogClients map to accurately reflect the updated live visitor count for each blog.

While the connectedClients map exists to track client connections, I've opted to use the blogClients map directly for live visitor count tracking. This decision simplifies the logic and ensures that the live visitor count remains accurate and up to date for each blog.

ย