How to implement Socket.io using Typescript

Typescript, according to its own website, is a “strongly typed programming language that builds on Javascript”. It can be seen as a superset of solutions and resources that makes Javascript more reliable.

Socket.IO is a “library that enables real-time…

Typescript, according to its own website, is a “strongly typed programming language that builds on Javascript”. It can be seen as a superset of solutions and resources that makes Javascript more reliable.

Socket.IO is a “library that enables real-time, bidirectional and event-based communication between browser and the server”. It makes it easier to construct websocket-based solutions where the server can send updates to the browser in real-time.

In this article we will create a simple application implementing Socket.io using Typescript where the browser is updated via a third party http request. In this case we will have an order listing which is updated every time a new order arrives.



Setting up the project’s structure

Let’s start by creating our server folder

mkdir websocket-typescript && cd websocket-typescript

Then initialize our project

npm init

set dist/app.js as entry point

In order to keep this project working as the updates come by, we will install our dependencies with specific versions:

# install typescript globally
npm install typescript -g

# dev-dependencies
npm i --save-dev @types/express@4.17.13 @types/node@16.6.2 ts-node@10.2.1 tslint@5.12.1 typescript@4.2.4

npm i --save class-transformer@0.3.1 class-validator@0.12.2 dotenv@10.0.0 express@4.17.1 routing-controllers@0.9.0 socket.io@4.1.3

# Initialize Typescript: 
tsc --init

Now open your favorite text-editor and go to the root of our project. You’ll find a tsconfig.json file there. This file indicates that it is a Typescript project.

Copy and paste this content into the tsconfig.json file replacing the initial one:

{
  "compilerOptions": {
      "module": "commonjs",
      "esModuleInterop": true,
      "target": "ES2015",
      "moduleResolution": "node",
      "sourceMap": true,
      "outDir": "dist",
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true
  },
  "lib": [
      "es2015"
  ]
}
  • "module": "commonjs" Is the usually used for Node projects;
  • "esModuleInterop": true Will make sure that our imports behave normally;
  • "target": "ES2015" Helps to support ES2015 code;
  • "moduleResolution": "node" Specifically implies that this is a Node project;
  • "sourceMap": true Enables the generations of .map files;
  • "outDir": "dist" This is where our output files will be generated;
  • "emitDecoratorMetadata": true Enables experimental support for emitting type metadata for decorators which works with the module;
  • "experimentalDecorators": true Enables experimental support for decorators;
  • "lib": ["es2015"] This includes a default set of type definitions;

Now create a folder named src and a server.ts in it. Our folder structure will be divided in two: http and websocket.
Folder structure for websocket

This will be the initial content of our server.ts file:

require('dotenv').config()
import 'reflect-metadata';

import {
   createExpressServer,
   RoutingControllersOptions
} from 'routing-controllers'

const port = process.env.APP_PORT || 3000;

const routingControllerOptions: RoutingControllersOptions = {
   routePrefix: 'v1',
   controllers: [`${__dirname}/modules/http/*.controller.*`],
   validation: true,
   classTransformer: true,
   cors: true,
   defaultErrorHandler: true
}

const app = createExpressServer(routingControllerOptions);

app.listen(port, () => {
   console.log(`This is working in port ${port}`);
});

Now in the console type

tsc && node dist/server.js

You should see this:
Application running on port
Note that we haven’t configured nodemoon in this project, so as we change the server, you’ll need to rerun this command



Socket.io in Node

So far nothing new. You’ve probably created lots of Node projects similar to this. Now here’s where the fun begins. In order to have access to our Socket Server Instance in different parts of our application we will implement the Singleton Design Pattern. Within the websocket folder create a file called websocket.ts. This will be its initial content:

import { Server } from 'socket.io';

const WEBSOCKET_CORS = {
   origin: "*",
   methods: ["GET", "POST"]
}

class Websocket extends Server {

   private static io: Websocket;

   constructor(httpServer) {
       super(httpServer, {
           cors: WEBSOCKET_CORS
       });
   }

   public static getInstance(httpServer?): Websocket {

       if (!Websocket.io) {
           Websocket.io = new Websocket(httpServer);
       }

       return Websocket.io;

   }
}

export default Websocket;

First we are importing the Server object from socket.io. Our class will inherit from it. Let’s take a look at the getInstance Method. It receives an optional parameter called httpServer and returns a Websocket instance. It checks if the private static attribute io is initialized. If not, it calls its own constructor and always returns a running instance of The Websocket implementation.

Let’s get back to our server.ts file now. To use the socket implementation we need to import it first:

import Websocket from './modules/websocket/websocket';

Now in order to correctly implement this we have to change the way that our http server is created. That is because the Server object, which our Websocket class inherits from, expects an instance of NodeJS’s default http. Therefore in the beginning of the server.ts file we must add:

import { createServer } from 'http';

Just after the creation of the constant app we must add:

const httpServer = createServer(app);
const io = Websocket.getInstance(httpServer);

Last but not least, change the app.listen part to

httpServer.listen(port, () => {
   console.log(`This is working in port ${port}`);
});

In order to separate the responsibilities of Sockets and Server, we must create a default pattern that each Socket class must implement. So add a file called mySocketInterface.ts to the websocket folder and add this to it:

import { Socket } from "socket.io";

interface MySocketInterface {

   handleConnection(socket: Socket): void;
   middlewareImplementation?(soccket: Socket, next: any): void

}

export default MySocketInterface;

This is important because every socket-based class that we create from now on will implement this interface which will guarantee that we have exactly the methods that we need.

Without further ado we can finally create our orders.socket.ts file within the websocket folder. This file will be responsible for handling every socket connection regarding Orders. You may create other files in the future for different parts of your application. This will be its initial content:

import { Socket } from "socket.io";
import MySocketInterface from "./mySocketInterface";

class OrdersSocket implements MySocketInterface {

   handleConnection(socket: Socket) {

        socket.emit('ping', 'Hi! I am a live socket connection');

    }

   middlewareImplementation(socket: Socket, next) {
       //Implement your middleware for orders here
       return next();
   }
}

export default OrdersSocket;

Since the OrdersSocket class implements MySocketInterface interface it is obligated to contain the handleConnection method. The middlewareImplementation method is optional and you can leave it out if you want.

Let’s get back to the websocket.ts file. We’ll now create a new method to initialize and handle each socket implementation that we have. This is what it will look like:

public initializeHandlers(socketHandlers: Array<any>) {
       socketHandlers.forEach(element => {
           let namespace = Websocket.io.of(element.path, (socket: Socket) => {
               element.handler.handleConnection(socket);
           });

           if (element.handler.middlewareImplementation) {
               namespace.use(element.handler.middlewareImplementation);
           }
       });
   }

don’t forget to change the import statement to

import { Server, Socket } from 'socket.io';

This function is supposed to receive an array which will contain elements with information about each socket path and handler.

Now let’s get back to our server.ts file and enhance it. Import the OrderSocket class and just after the creation of the constant io add the following:

io.initializeHandlers([
   { path: '/orders', handler: new OrdersSocket() }
]);

Great! To test all of this I’ve created a really simple html file which if you open in your browser you should see a message on screen if everything is right. You can download it here



Socket.io in the browser

Let’s get started on the table and Http part no. We’ll create a simple page to display information about the orders. I’m using bootstrap to make it slightly easier in terms of style, but feel free to use any framework of your choice.

You can download the index.html file here. We will only focus on the javascript part. The first thing we have to do once our page is loaded is to check for socket connection and once it is established emit an event requesting the initial orders listing, so create a index.js file and paste this as initial content:

const socket = io("http://localhost:3000/orders");

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

socket.on('orders_updated', (orders) => {
    populateTable(orders.data);
})

socket.on('disconnect', () => {
    console.error('Ops, something went wrong');
});

function populateTable(data) {
    data.forEach(order => {
        document.querySelector('#orders-table tbody')
            .insertAdjacentHTML('afterend', createTableRow(order));
    });
}

function createTableRow(order) {
    let tRow = `<tr>
            <th scope="row">${order.id}</th>
            <td>${order.date}</td>
            <td>${order.total}</td>
            <td>${order.status}</td>
        </tr>`;

    return tRow;

}

We’ll now get back to Node to create the endpoint in which we’ll receive new orders. It is a good practice to set your business rules in a service file. And that’s what we’ll do. Create a libs folder and a orders.service.ts file in it:
lib folder

This will be the file content:

import Websocket from "../modules/websocket/websocket";

class OrdersService {

    public insertOrder(order) {
        //save in your database

        //send the update to the browser
        this.updateSockets(order);
    }

    private updateSockets(order) {
        const io = Websocket.getInstance();
        io.of('orders').emit('orders_updated', { data: [order] });
    }
}

export default OrdersService;

This is quite simple, but we’re getting an instance of the Websocket class and emitting an event which our frontend file will listen and then update our table.

Now create a file orders.controller.ts within the http folder. This will be its content:

import { JsonController, Post, Body } from "routing-controllers";
import OrdersService from "../../libs/orders.service";

@JsonController('/orders', { transformResponse: true })
class OrdersController {

   @Post('/')
   insertOrder(@Body() order: any) {
       let orderService = new OrdersService();
       orderService.insertOrder(order);

       return {
           status: 200,
           success: true
       };
   }
}

export default OrdersController;

Here the routing-controllers lib is helping us set a path to the order route for our web server and we’re simpl calling the orders.service file that we just created.

Ok so go ahead to postman and send a POST request to http://localhost:3000/v1/orders/ with this content:

{
   "id": "4",
   "date": "2021-11-05",
   "total": "$13.00",
   "status": "Pending"
}

Don’t forget to rerun the command to compile typescript in Node and check the table. It should be updating as the requests are sent.

orders table



That’s all folks

This is just a sketch and one of the many ways to build a Socket.io based application. Feel free to leave a comment on possible better solutions =]



References

https://www.typescriptlang.org/
https://socket.io/docs/v4/
https://socket.io/docs/v4/namespaces/
https://socket.io/docs/v4/middlewares/
https://www.typescriptlang.org/tsconfig
https://dev.to/rajat19/create-a-new-node-js-project-in-typescript-nao
https://developer.mozilla.org/pt-BR/docs/Web/API/Element/insertAdjacentHTML
https://github.com/typestack/routing-controllers


Print Share Comment Cite Upload Translate
APA
Nicolas Felix | Sciencx (2024-03-29T14:59:31+00:00) » How to implement Socket.io using Typescript. Retrieved from https://www.scien.cx/2022/01/16/how-to-implement-socket-io-using-typescript/.
MLA
" » How to implement Socket.io using Typescript." Nicolas Felix | Sciencx - Sunday January 16, 2022, https://www.scien.cx/2022/01/16/how-to-implement-socket-io-using-typescript/
HARVARD
Nicolas Felix | Sciencx Sunday January 16, 2022 » How to implement Socket.io using Typescript., viewed 2024-03-29T14:59:31+00:00,<https://www.scien.cx/2022/01/16/how-to-implement-socket-io-using-typescript/>
VANCOUVER
Nicolas Felix | Sciencx - » How to implement Socket.io using Typescript. [Internet]. [Accessed 2024-03-29T14:59:31+00:00]. Available from: https://www.scien.cx/2022/01/16/how-to-implement-socket-io-using-typescript/
CHICAGO
" » How to implement Socket.io using Typescript." Nicolas Felix | Sciencx - Accessed 2024-03-29T14:59:31+00:00. https://www.scien.cx/2022/01/16/how-to-implement-socket-io-using-typescript/
IEEE
" » How to implement Socket.io using Typescript." Nicolas Felix | Sciencx [Online]. Available: https://www.scien.cx/2022/01/16/how-to-implement-socket-io-using-typescript/. [Accessed: 2024-03-29T14:59:31+00:00]
rf:citation
» How to implement Socket.io using Typescript | Nicolas Felix | Sciencx | https://www.scien.cx/2022/01/16/how-to-implement-socket-io-using-typescript/ | 2024-03-29T14:59:31+00:00
https://github.com/addpipe/simple-recorderjs-demo