Real-time communication with Socket.io using Typescript
Tags: typescript, javascript, node, tutorial
Published: 2022-01-16 13:49:02

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
```bash
mkdir websocket-typescript && cd websocket-typescript
```
Then initialize our project
```bash 
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: 
```bash 
# 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:
```json
{
  "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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w9l5intv6e1xzwiubn3t.png)

This will be the initial content of our `server.ts` file:
```typescript
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 
```bash
tsc && node dist/server.js
```
You should see this: 
![Application running on port](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mwk578vpxtici0382ehz.jpeg)
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: 

```typescript
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:
```typescript
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:

```typescript
import { createServer } from 'http';
```
Just after the creation of the constant `app` we must add: 
```typescript
const httpServer = createServer(app);
const io = Websocket.getInstance(httpServer);
```
Last but not least, change the `app.listen` part to
```typescript
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: 

```typescript
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: 

```typescript
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:

```typescript
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 
```typescript
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:

```typescript
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](https://github.com/nickFelix/websocket-typescript/blob/main/resources/index.test.html)

## 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](https://github.com/nickFelix/websocket-typescript/blob/main/resources/index.html). 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:
```javascript
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/komwur3mwj6curp7al0s.png)

This will be the file content: 
```typescript
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: 

```typescript
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: 
```json
{
   "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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gwehge4tw9o74ql5u8vw.png)

## 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