📜 ⬆️ ⬇️

TypeScript real-time applications: chat development using WebSocket, Node and Angular

I recently created a simple chat using only TypeScript. The main goal of this project was to write an application that demonstrates the use of this language both on the client and on the server. The chat client is based on the latest version of Angular. The server is based on Node.js. The interaction between them is organized using the WebSocket protocol.

From this material you will learn how to create the chat in question, from scratch. By the way, how does working with him look like?


Chat, written in TypeScript

About real-time applications


In accordance with this definition from Wikipedia, a real-time application allows interested entities to receive information immediately after it was published, without the need to periodically poll the source of information. Thus, this type of application should give users the feeling that certain actions occur instantaneously, without delays.
')

WebSocket protocol


WebSocket is a protocol that allows you to organize a bidirectional data channel. In our case, this means that the browser and the web server can communicate in real time by sending messages to each other when there is an open connection between them.


Data exchange using the WebSocket protocol

Application structure


We will place the code related to the client and server parts of the application in separate folders. The structure of the finished application is shown below. Details will be discussed below when we talk about the key files of our project.

server/ |- src/ |- package.json |- tsconfig.json |- gulpfile.js client/ |- src/ |- package.json |- tsconfig.json |- .angular-cli.json 

Choice of implementation of WebSocket


Since the WebSocket protocol is a specification , you can find several of its practical implementations . Here you can use JavaScript , TypeScript, or any other programming language.

In this case, we will use the Socket.IO library. It is one of the fastest and most reliable libraries, realizing the possibility of real-time data exchange.

Why use TypeScript on the server?


TypeScript offers the programmer great features , the development team keeps the language up to date. In addition, the use of typing reduces the number of errors in the code, in comparison with the use of conventional JS. For me, these reasons are quite enough to use TS on the server.

Initialization of the server application


Create a package.json file and install the following dependencies:

 npm install --save express socket.io @types/express @types/socket.io 

In addition, you will need to install some development dependencies in order to integrate gulp and typescript into the project, all this will be useful to us during the creation and assembly of the finished project:

 npm install --save-dev typescript gulp gulp-typescript 

TypeScript Compiler Setup


Create a tsconfig.json file and put the following into it:

 { "files": [   "src/*.ts",   "src/model/*.ts" ], "compilerOptions": {   "target": "es5" } } 

Description of the data model


Using the possibilities of static typing, we will create a small data model:

 export class User {   constructor(private name: string) {} } export class Message {   constructor(private from: User, private content: string) {} } export class ChatMessage extends Message{   constructor(from: User, content: string) {       super(from, content);   } } 

Take a look at the server/src directory structure:

 server/ |- src/  |- model/     |- message.model.ts     |- user.model.ts  |- index.ts  |- server.ts |- package.json |- tsconfig.json |- gulpfile.js 

The implementation of the server side of the chat


The main files in the server directory are index.ts and chat-server.ts . The first allows you to create and export the ChatServer application, while the second contains the express and Socket.IO configurations:

Here is the code for the index.js file:

 import { ChatServer } from './chat-server'; let app = new ChatServer().getApp(); export { app }; 

Here is the chat-server.ts :

 import { createServer, Server } from 'http'; import * as express from 'express'; import * as socketIo from 'socket.io'; import { Message } from './model'; export class ChatServer {   public static readonly PORT:number = 8080;   private app: express.Application;   private server: Server;   private io: SocketIO.Server;   private port: string | number;   constructor() {       this.createApp();       this.config();       this.createServer();       this.sockets();       this.listen();   }   private createApp(): void {       this.app = express();   }   private createServer(): void {       this.server = createServer(this.app);   }   private config(): void {       this.port = process.env.PORT || ChatServer.PORT;   }   private sockets(): void {       this.io = socketIo(this.server);   }   private listen(): void {       this.server.listen(this.port, () => {           console.log('Running server on port %s', this.port);       });       this.io.on('connect', (socket: any) => {           console.log('Connected client on port %s.', this.port);           socket.on('message', (m: Message) => {               console.log('[server](message): %s', JSON.stringify(m));               this.io.emit('message', m);           });           socket.on('disconnect', () => {               console.log('Client disconnected');           });       });   }   public getApp(): express.Application {       return this.app;   } } 

Server classes


The code above gives the following classes and relationships between them:


Server class diagram

Build and run the server


In order to get the JavaScript files needed by the V8 engine on which Node.js is based, add the build task to the gulpfile.js file:

 var gulp = require("gulp"); var ts = require("gulp-typescript"); var tsProject = ts.createProject("tsconfig.json"); gulp.task("build", function () {   return tsProject.src()       .pipe(tsProject())       .js.pipe(gulp.dest("./dist")); }); 

As you can see, the output of the build process (JS files) will be located in the dist directory. To complete the build, you need the following command:

 gulp build 

Now, in order to start the server, you need to use the following command:

 node dist/index.js 

Chat client development


Create a folder for the client using Angular CLI :

 ng new typescript-chat-client --routing --prefix tcc --skip-install 

Install the project dependencies. Here you can run the npm install command, but I prefer to use Yarn in this step:

 cd typescript-chat-client yarn install 

Adding the Angular Material component set to a project


In order to take advantage of the Angular Material component set in a project that was created using the Angular CLI, look at the fresh guide on material.angular.io and proceed in accordance with it.

In accordance with the recommendations on the structure of Angular-projects, we will create the modules shared and material :

 client/ |- src/  |- app/     |- chat/     |- shared/        |- material/           |- material.module.ts        |- shared.module.ts     |-app.module.ts 

This can be done from the command line:

 ng generate module shared --module app ng generate module shared/material --module shared 

To evaluate the relationship between these modules, analyze the files app.module.ts and shared.module.ts .

Connect express and Socket.IO


Now we need to connect the express and socket.io modules to our client application:

 npm install express socket.io --save 

Chat Modules & Components


Before creating components for the chat, create a new module:

 ng generate module chat --module app 

Now add a component to this module:

 ng generate component chat --module chat 

In order to use web sockets and your own models, create another shared folder. This time - inside the chat directory:

 ng generate service chat/shared/services/socket --module chat ng generate class chat/shared/model/user ng generate class chat/shared/model/message 

The result should be the following structure:

 client/ |- src/  |- app/     |- chat/        |- shared/          |- model/             |- user.ts             |- message.ts          |- services/             |- socket.service.ts     |- shared/     |-app.module.ts 

Observables and Web Sockets


Since our Angular application supports RxJS, you can use observable objects to work with Socket.IO events. Therefore, the file socket.services.ts will look like this:

 import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Observer } from 'rxjs/Observer'; import { Message } from '../model/message'; import { Event } from '../model/event'; import * as socketIo from 'socket.io-client'; const SERVER_URL = 'http://localhost:8080'; @Injectable() export class SocketService {   private socket;   public initSocket(): void {       this.socket = socketIo(SERVER_URL);   }   public send(message: Message): void {       this.socket.emit('message', message);   }   public onMessage(): Observable<Message> {       return new Observable<Message>(observer => {           this.socket.on('message', (data: Message) => observer.next(data));       });   }   public onEvent(event: Event): Observable<any> {       return new Observable<Event>(observer => {           this.socket.on(event, () => observer.next());       });   } } 

Now we are ready to respond to messages from the server, so consider the chat.component.ts file (the code for Material and UI events is omitted here):

 import { Component, OnInit } from '@angular/core'; import { Action } from './shared/model/action'; import { Event } from './shared/model/event'; import { Message } from './shared/model/message'; import { User } from './shared/model/user'; import { SocketService } from './shared/services/socket.service'; @Component({ selector: 'tcc-chat', templateUrl: './chat.component.html', styleUrls: ['./chat.component.css'] }) export class ChatComponent implements OnInit { action = Action; user: User; messages: Message[] = []; messageContent: string; ioConnection: any; constructor(private socketService: SocketService) { } ngOnInit(): void {   this.initIoConnection(); } private initIoConnection(): void {   this.socketService.initSocket();   this.ioConnection = this.socketService.onMessage()     .subscribe((message: Message) => {       this.messages.push(message);     });   this.socketService.onEvent(Event.CONNECT)     .subscribe(() => {       console.log('connected');     });       this.socketService.onEvent(Event.DISCONNECT)     .subscribe(() => {       console.log('disconnected');     }); } public sendMessage(message: string): void {   if (!message) {     return;   }   this.socketService.send({     from: this.user,     content: message   });   this.messageContent = null; } public sendNotification(params: any, action: Action): void {   let message: Message;   if (action === Action.JOINED) {     message = {       from: this.user,       action: action     }   } else if (action === Action.RENAME) {     message = {       action: action,       content: {         username: this.user.name,         previousUsername: params.previousUsername       }     };   }   this.socketService.send(message); } } 

As soon as ChatComponent initialized, the component subscribes to the observed SocketService objects in order to receive connection-related events or incoming messages.

The sendMessage and sendNotification will send, respectively, messages and notifications through the same service. Notifications are used to notify the system that a new user has joined the chat and to rename the chat participants.

Results


From this material, you learned how to use TypeScript to write a real-time application - a chat, in which TS is used both on the client and on the server, and in which technologies such as WebSockets, Node.js and Angular are involved. The source code of the project can be found here . And here is a working chat (open a couple of tabs in the browser with this page in order to experience it).

Dear readers! Do you use TypeScript to develop server applications?

Source: https://habr.com/ru/post/347306/


All Articles