Building Real-Time Application with Socket.io and ExpressWebJs

Building Real-Time Application with Socket.io and ExpressWebJs

In the world of web development, real-time communication is becoming increasingly essential for creating interactive and engaging applications. Whether it's a chat application, a live-updating dashboard, or a collaborative editing tool, the ability to transmit data in real-time is crucial. Socket.io, a popular library for real-time web applications, makes this task easier by providing a simple and reliable way to implement bidirectional communication between clients and servers. When combined with the power and safety of TypeScript, developers can create robust and scalable real-time applications.

Prerequisites

Before we dive into building a real-time application with ExpressWebJs and Socket.io, make sure you have the following installed:

  1. ExpressWebJs

  2. A code editor of your choice (e.g., Visual Studio Code)

  3. Nodejs

Setting Up the Project

To start with ExpressWebJs, visit the installation guide at the official website at ExpressWebJs.com.

Once ExpressWebJs is installed, open it in your code editor and install project dependencies by running

yarn install

Next, install socket.io with the command below:

yarn add socket.io

Once that is done, run the following command to generate your .env file from example.env

cp example.env .env

I will need to turn off the database connection as I may not need it in this tutorial. To do that, set DB_SHOULD_CONNECT to false in your env.

ExpressWebJs comes with a predefined socket configuration which is located in the Socket.ts file in the Config directory.

/*
|--------------------------------------------------------------------------
| Socket.io Config  
|--------------------------------------------------------------------------
| npm i --save socket.io
*/
export default {
  /*
  |--------------------------------------------------------------------------
  | Path
  |--------------------------------------------------------------------------
  |
  | The base path on which the socket.io server will accept connections.
  | Type {String}
  */
  path: "/expressweb-ws",

  /*
  |--------------------------------------------------------------------------
  | Serve Client
  |--------------------------------------------------------------------------
  |
  | Whether to serve the client files
  | Type {Boolean} 
  */
  serveClient: true,

  /*
  |--------------------------------------------------------------------------
  | Ping Timeout
  |--------------------------------------------------------------------------
  |
  | How many ms without a pong packet to consider the connection closed
  | Type {Number}
  */
  pingTimeout: 5000,

  /*
  |--------------------------------------------------------------------------
  | Ping Interval
  |--------------------------------------------------------------------------
  |
  | How many ms before sending a new ping packet
  |
  */
  pingInterval: 25000,

  /*
  |--------------------------------------------------------------------------
  | Upgrade Timeout
  |--------------------------------------------------------------------------
  |
  | How many ms before an uncompleted transport upgrade is cancelled
  |
  */
  upgradeTimeout: 10000,

  /*
  |--------------------------------------------------------------------------
  | MaxHttpBufferSize    
  |--------------------------------------------------------------------------
  |
  | How many bytes or characters a message can be, before closing the session (to avoid DoS).
  |
  */
  maxHttpBufferSize: 10e7,

  /*
  |--------------------------------------------------------------------------
  | Transports
  |--------------------------------------------------------------------------
  |
  | Transports to allow connections to
  |
  */
  transports: ["polling", "websocket"] as Transport[],

  /*
  |--------------------------------------------------------------------------
  | AllowUpgrades    
  |--------------------------------------------------------------------------
  |
  | Whether to allow transport upgrades
  |
  */
  allowUpgrades: true,

  /*
  |--------------------------------------------------------------------------
  | Socket Cors
  |--------------------------------------------------------------------------
  |
  | Whether to allow transport upgrades
  |
  */
  cors: {
    origin: "*",
    methods: ["GET", "POST"],
    allowedHeaders: ["Origin", "Content-Type", "Content-Length", "Authorization", "Accept", "X-Requested-With"],
    credentials: true,
  },
};

type Transport = "polling" | "websocket";

You can update the setting to suit your needs, but I will be working with the default socket configuration.

Socket Service Provider

Once the configuration is done, create a SocketServiceProvider class in the App/Providers directory and add the following code:

import { Server } from "socket.io";
import config from "Config/Socket";
import { ServiceProvider } from "Elucidate/Support/ServiceProvider";

export class SocketServiceProvider extends ServiceProvider {
  /**
   * Load any service after application boot stage
   * @return void
   */

  public register(): void {
    this.bindAsSingletonFunction("io", () => {
      return new Server(this.use("ProtocolServer") as any, config);
    });
  }
}

In the code above, we imported Server from socket.io, imported our config from Config/socket, and finally imported ServiceProvider. We then extended the ServiceProvider. In the register method, we created our socket server using ExpressWebJs ProtocolServer and passed in our configuration as the second argument. We then registered it with the name io as a singleton function to the IOC.

To make sure ExpressWebJs is aware of our newly created SocketServiceProvider, we need to register it in providers collection in the Config/App.ts file.

providers: [
    /*
     * ExpressWebJS Framework Service Providers...
     */
    "Elucidate/Route/RouteConfigServiceProvider::class",
    /*
     * Application Service Providers...
     */
    "App/Providers/AppServiceProvider::class",
    "App/Providers/RouteServiceProvider::class",
    "App/Providers/EventServiceProvider::class",
    "App/Providers/RepositoryServiceProvider::class",
    "App/Providers/SocketServiceProvider::class", 👈 //SocketServiceProvider
  ],

With that in place, we are done with the configuration phase.

Notification Class

We are ready to start emitting and receiving messages, to do that, let's create a real-time notification service class. You can name your class anything.

import { INotificationModel } from "App/Model/Types/INotificationModel";

export abstract class RealTimeNotificationService {
  abstract notifyUser(recipientId: string, notification: INotificationModel): Promise<void>;
}

Then the implementation class

import { INotificationModel } from "App/Model/Types/INotificationModel";
import { RealTimeNotificationService } from "../RealTimeNotificationService";
import { Autowire } from "Elucidate/Support/ServiceProvider";
import { Server, Socket } from "socket.io";

export class RealTimeNotificationServiceImpl implements RealTimeNotificationService {
  private io = Autowire<Server>("io");
  public socket: Socket | null = null;
  constructor() {
    this.io.on("connection", (socket: Socket) => {
      console.log(`Socket id: ${socket.id} is connected`);
      this.socket = socket;
    });
  }

  public async notifyUser(recipientId: string, notification: INotificationModel): Promise<void> {
    this.socket?.emit(`${recipientId}_notification`, notification);
  }
}

This class has a method called notifyUser method which accepts the recipientId and notification object. It then uses the emit method of the socket to emit (send) the notification to the topic in this case if the recipient id is 123, it will be 123_notification.

Before we continue, let's register RealTimeNotificationServiceImpl in ExpressWebJs Container.
Head over to App/Providers/AppServiceProvider.ts in the register method and add

this.bindAsSingletonClass(RealTimeNotificationService, RealTimeNotificationServiceImpl);

you can now inject RealTimeNotificationService in any class and use the notifyUser method.

export class NotificationServiceImpl extends BaseService implements NotificationService {
  constructor(private realTimeNotificationService: RealTimeNotificationService, private notificationRepository: NotificationRepository) {
    super();
  }

  public async createNotification(data: CreateNotificationDto): Promise<ResponseType<void>> {
    try {
      const message = this.processNotificationMessage(data.senderName, data.type);
      if (message) {
        for await (let recipientId of data.recipientIds) {
          const notificationRecord = {
            //...notification data
          };
          const savedNotification = await this.notificationRepository.save(notificationRecord);

          await this.realTimeNotificationService.notifyUser(recipientId, { id: savedNotification.id, ...notificationRecord });
        }

        return SuccessResponse({ message: "Notification successfully saved" });
      }
      return ErrorResponse({ message: "Can't construct notification message" });
    } catch (error) {
      return ErrorResponse({ code: this.statusCode.EXPECTATION_FAILED, message: this.getFriendlyErrorMessage(error) });
    }
  }
}

Socket Client

Let’s switch from server to client and subscribe to the chat channel.

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Socket.io test</title>
  </head>
  <body>
    <h4>Hello from Socket</h4>
    <script src="http://127.0.0.1:5400/expressweb-ws/socket.io.js"></script>
    <script>
      const socket = io.connect("http://localhost:5400/", {
        path: "/expressweb-ws",
      });
      socket.on("123_notification", (data) => {
        console.log(data);
      });
    </script>
  </body>
</html>

The above HTML code loads to the server socket.io.js via http://127.0.0.1:5400/expressweb-ws/socket.io.js and then connects using io.connect

It listens to events using socket.on, and then consoles the data.

Conclusion:

Congratulations! You have successfully set up socketIO using the socket.io library in ExpressWebJs.

This tutorial provides a fundamental introduction to using SocketIO in ExpressWebJs. From here, you can explore more advanced features and configurations to suit your specific use case. Happy coding!

Kindly:

Star to support ExpressWebJs on GitHub

Also, follow us on Twitter @ ExpressWebJs