Building chat with Odi (Node.js)
Tags: javascript, typescript, node, opensource
Published: 2019-01-18 21:06:19

---
title: Building chat with Odi (Node.js)
published: true
description: Real-time communication, project setup, templating and much more
tags: #javascript,#typescript,#nodejs,#opensource
cover_image: https://cdn-images-1.medium.com/max/1100/1*1sgX66LJwz_v0hyL83JGhQ.png
canonical_url: https://medium.com/@dantsk/buildin-chat-with-odi-node-js-e77ecd3891b2
---
# Republish
Sorry for republishing the same story. My teammate accidentally deleted the previous publication. He thought that we published it before the release date 😢 Thanks for understanding 😜

# Introduction

The chat application is the very common example that used to show simple real-time communication between client and server. This tutorial describes how it can be easily done with Odi,TypeScript server-side framework for
Node.js.

# Project Setup

We are going to develop the application that not only establishes the real-time communication channel but also renders frontend to the client, including required assets.

## Basic Settings

Let’s set up the Odi project. First of all, we should initialize `package.json`
and `tsconfig.json` files. We can do it with two simple commands.
```bash
    npm init -y
    tsc --init
```

And install **Odi**.
```bash
    npm install odi
```

Also, we need to modify `tsconfig.json` file, as there are few options that must be edited. Odi actively uses decorators and metadata, so we need to enable these features.
```json
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
```

Another thing is `target` option. By default, it set to`es5` but there are
several things that are not supported in is this specification. As we are
progressive, let’s set it to the latest version
```json
    "target": "ES2018"
```

## Project Structure

We are going to have different folders for views, assets and server source code.

![](https://cdn-images-1.medium.com/max/880/1*2jLFB3kGBqfnLp3YfUWvXQ.png)

* **src**— server source code.
* **views**— JSX templates that will be rendered to clients.
* **assets**— client-side *JS* and *CSS* files.

JSX templates are *tsx* files that must be compiled. Add **views** folder to
*rootDirs* in `tsconfig.json` file and setup *outDir*.
```json
    "outDir": "./build",
    "rootDirs": ["./src", "./views"]
```

# Gettings started

Odi is based on the *Dependency Injection* pattern, so every application component will be automatically imported, instantiated and injected.

Only folder with source files must be specified, then Odi can scan it for importing application components (Controllers, Services, Repositories and etc).

## Main File

Create `index.ts` file in `src` folder. It will be the server entry point file.
```typescript
    import { Core } from "odi";
    import { join } from "path";

    new Core({
        sources: __dirname,
        server: {
            port: 8080,
            socket: true,
            static: {
                root: join(__dirname, '../../assets'),
                prefix: '/assets'
            }
        }
    }).listen(() => console.log("Server successfully started!"));
```

We just need to instantiate `Core` class. `Core` constructor accepts a single argument, settings object. There are a lot of possible options, but for now, we need only several of them.

First of all, we need to specify `sources` property. It’s required setting for Odi application. As `index.ts` file in `src` folder, which we choose for server-side code, we can use `__dirname` to set current directory.

`port` property is also required. It binds the server on the specified port.

Now about the following part:
```typescript
    socket: true,
    static: {
         root: join(__dirname, '../../assets'),
         prefix: '/assets'
    }
```

We must enable sockets and set options for serving static files All files from the **assets** folder are available by URL with `/assets` prefix.

## Installing Dependencies

Odi framework automatically includes only several packages that are required. All other dependencies for different features are optional, so they need to be installed only if you use a certain feature.

For example, if you are going to build a simple REST server, you don’t need GraphQL, WebSockets, SSR and other packages.

We want to have WebSockets and Templating (JSX) in our chat application. So, let’s install missing packages:
```bash
    npm install socket.io react react-dom
```

That’s all, Odi will automatically import it. As you can see, [socket.io](http://socket.io)is used under the hood for real-time functionality. Also React packages is required for templates processing.

Now we can start writing our code :)

# Application

We are going to create a web server, that renders HTML to the client, using
templates, serves files for the client (JS, CSS) and set up a real-time
communication channel using WebSockets for chat. Let’s add history to our chat. So, the last 10 messages will be saved in our system.

## Message and History

`Message` will be pretty simple, only `username` and `text` fields. We can do it
with a simple interface, as we are not going to use a database.

```typescript
    export interface Message {
        username: string;
        text: string;
    }
```

And history service
```typescript
    @Service()
    export default class HistoryService {
        private store: Message[] = [];
        
        getMessages() {
            return this.store;
        }

        addMessage(message: Message) {
            if(this.store.length > 10)
                this.store.shift();

            this.store.push(message);
        }
    }
```
Our store is a simple array of messages. And few methods for store management. If we get more than 10 messages, we simply remove the first message from the array.

As you can see, `Service` decorator was used for `HistoryService` class to set is as a service component. Service is singleton in Dependency Injection Container. Now it can be injected into others application components.

Put all this code in `history.ts` file in `src/services` folder.

## Web Socket

Create `chat.socket.ts` file in the `src/sockets` directory with the following
code.
```typescript
    import { Socket, OnEvent, ISocket, Autowired } from "odi";
    import HistoryService, { Message } from "../services/history";

    @Socket('chat')
    export default class ChatSocket extends ISocket {

        @Autowired()
        history: HistoryService;

        @OnEvent('massage:send') 
        onmessage(message: Message) {
           this.history.addMessage(message); 
           this.emit('message:new', message);
        }
    }
```
We defined `/chat` namespace with handler for `message:send` event. If
`message:send` event is fired, all clients that connected to this namespace will be notified with `message:new` event and message data.

As you can notice `Socket` decorator defines namespaces. Leading slash is not required. To set up method as the handler for certain event, use `OnEvent`
decorator, that accepts event name as the argument.

Also, we injected `HistoryService` using `Autowired` decorator. `history` field
of `ChatSocket` class will be initialized by Odi, so you don’t need to do
anything additional.

The only thing, you can see such error from TypeScript
```
    [ts] Property 'history' has no initializer and is not definitely assigned in the constructor.
```

Odi automatically initializes injected fields, so just disable this check in
`tsconfig.json` 
```json
    "strictPropertyInitialization": false
```

# Templating (JSX)

There a lot of templating processors  —  EJS, Jade, Pug. But there are a lot of
limitations and inconveniences with those technologies. In most cases, to have IntelliSense and code highlight for templates, you need to install an extension for IDE/Editor. 

In Odi, JSX powered by React is used for templating. You can simply create components with JSX. But remember, it’s only for templates, any logic, listeners or client-side code will be ignored during rendering. <br> *(Currently, we are working on full SSR.Hope it will be released soon)*

We need to tell TypeScript compiler, that we are going to use React JSX.<br> In `tsconfig.json` 
```json
    "jsx": "react"
```

## Layout

Let’s create our layout component `layout.view.tsx` that will be a wrapper for all pages. As was mentioned above, all templates will be in **views** folder. 
```react
    import React, { SFC } from 'react';

    export const Html: SFC = ({ children }) => (
        <html lang="en">
            <head>
                <meta charSet="UTF-8" />
                <meta name="viewport" />
                <meta httpEquiv="X-UA-Compatible" content="ie=edge"/>
                <link href="/assets/index.css" type="text/css" ... />
                <title> Simple chat </title>
            </head>
            <body>
                {children}
            </body>

            <script src="path/to/socket.io" />
            <script src="/assets/index.js" />
        </html>
    )
```
For *socket.io-client* library we can use CDN. So simply replace
`path/to/socket.io` in the script tag with the following link
`https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js`

Client js file was specified in the second script tag. We will create it a
little bit later in **assets** folder.

## Chat Components

Actually, we need 3 components for our chat:

1.  Message representation
1.  Controls (Send button, message input, username input)
1.  Chat container

I think we can put all these components in 1 file, `chat.view.tsx` 
```react
    import React from 'react';
    import { Message } from './services/history.service';

    export const ChatMessage = ({ username, text }: Message) => (
        <div>
            <b>{username}: </b>
            <span>{text}</span>
        </div>
    )
```
We can use `Message` interface as props type for `ChatMessage` component.

Let’s add chat controls. `id` attribute was used for convenience, as we are
going to use js on the client side without any libs or frameworks.
```react
    export const ChatControlls = () => (
        <div className="message-box">
            <input placeholder="User" id="user-input" />
            <input placeholder="Message" id="message-input" />
            <button> Send </button>
        </div>
    )
```
And the last thing, chat wrapper.
```react
    interface ChatProps {
        messages: Message[];
    }

    export const Chat = ({ messages }: ChatProps) => (
        <div className="chat">
            <div className="container">
              {messages.map(msg,i) => <ChatMessage key={i} {...msg} /> )}
             </div>
            <ChatControlls />
        </div>
    )
```
This component accepts an array of messages (our history) in props to render it on page load. 

Now we can put everything together and define our page component `page.view.tsx`
```react
    import React from 'react';
    import { Chat } from './chat.view';
    import { Html } from './layout.view';
    import { Message } from './services/history.service';

    interface ChatPageProps {
        history: Message[];
    }

    export const ChatPage = ({ history }: ChatPageProps) => (
        <Html>
            <Chat messages={history} />        
        </Html>
    )
```
That’s all about templating for our chat application. I have several lines of
CSS that I will include it in the source code, that you can find at the end of
the article. 

We can move to controllers. 

# Controllers

Controllers serve as a simple yet powerful routing mechanism. Controller methods are mapped to web server paths. The value returned by the method is sent as the response.

In order to create a Controller, you must use the `@Controller` decorator and inherit the `IController` class. The decorator sets the component type, so the DI (dependency injection) container can detect what the class will be used for.

For our chat, we need only one controller to render a template to the client. As we are going to use JSX inside the controller file, it must have `tsx` file
extension. 

So, let’s create `render.controller.tsx` in `src/controllers` folder.
```react
    import React from 'react';
    import { Controller, IController, Get, Autowired } from "odi";
    import { ChatPage } from '../../views/page.view';
    import HistoryService from '../services/history.service';

    @Controller()
    export default class RenderController extends IController {

        @Autowired()
        history: HistoryService;

        @Get index() {
            return <ChatPage history={this.history.getMessages()}/>;
        }
    }
```
As you can see, we injected our `HistoryService` into `history` property. Also, the handler for `/` path with `Get` method was defined. We can simply return our JSX component as a result, Odi automatically detects that it’s a template and renders it as simple HTML for the client (web browser).

# Starting Application

Now, we can start our application and see what we got. Let’s specify `start` script in `package.json` file:
```json
    "scripts": {
        "start": "tsc && node build/src/index.js"
    }
```
Running `npm start` command compile our source code and run server entry file.

![](https://cdn-images-1.medium.com/max/880/1*MOMjK0wTD3qdXbVLot83qQ.png)

Let’s open the browser and check `localhost:8080` 

![](https://cdn-images-1.medium.com/max/880/1*FW2ep25-HbxJQCpewczhPQ.png)

As you can see, we have just empty chat without any functionality, as we did not specify the client `index.js` into **assets** folder.

# Client 

First of all, let’s get references for chat container and controls.
```javascript
    const button = document.querySelector('button');

    const messageInput = document.querySelector('#message-input');
    const usernameInput = document.querySelector('#user-input');
    const container = document.querySelector('.container');
```
When a new message comes, we need to append it as a child in `container`
element. We need the function for creating elements that represent messages.
```javascript
    function createMessage({ username, text }) {
        const element = document.createElement('div');
        
        element.innerHTML = `
            <b>${username}: </b>
            <span>${text}</span>
        `;

        return element;
    }
```
Then, let’s connect to our `chat` namespace and add the event handler for `message:new` event. When this event is fired, the message element will be appended to the container.
```javascript
    const socket = io('/chat');
    socket.on('message:new', message => {
        const messageElement = createMessage(message);
        container.appendChild(messageElement);
    });
```
And the last step, `onclinck` handler for our button. 
```javascript
    button.onclick = () => {
        socket.emit('massage:send', { 
            text: messageInput.value, 
            username: usernameInput.value
        });

        messageInput.value = "";
    }
```
We are collecting data from inputs and sending it as `message:send` event. Also, the message input text will be cleared after every send. 
 
Now we can refresh the page, and see what we have got.

![](https://cdn-images-1.medium.com/max/880/1*eUyiuOZdH4Fe55QAHhb5yQ.gif)

After refreshing the page, we will have history our messaging.

## Sandbox

You can check the source code and interact with the application right here:
{% codesandbox 30n2y702m %}

## PS

Thanks for the reading! If you like Odi, please support us with a simple start on GitHub
{% github Odi-ts/odi %}


Also, if you are looking for more information, you can check previous articles and docs:

1.  [Docs](https://odi.gitbook.io)
1.  [First Article](https://medium.com/@dantsk/odi-ddd54848c1c3)
1.  [Second
Article](https://medium.com/@dantsk/odi-jsx-openapi-and-more-ca67555527b)

If you have any ideas or questions, feel free to leave them! Thanks a lot! :)

<br>