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 and files. We can do it with two simple commands. package.json tsconfig.json npm init -ytsc --init And install . Odi npm install odi Also, we need to modify file, as there are few options that must be edited. Odi actively uses decorators and metadata, so we need to enable these features. tsconfig.json "experimentalDecorators": true,"emitDecoratorMetadata": true Another thing is option. By default, it set to 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 target es5 "target": "ES2018" Project Structure We are going to have different folders for views, assets and server source code. — server source code. src — JSX templates that will be rendered to clients. views — client-side and files. assets JS CSS JSX templates are files that must be compiled. Add folder to in file and setup . tsx views rootDirs tsconfig.json outDir "outDir": "./build","rootDirs": ["./src", "./views"] Gettings started Odi is based on the pattern, so every application component will be automatically imported, instantiated and injected. Dependency Injection 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 file in folder. It will be the server entry point file. index.ts src 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 class. constructor accepts a single argument, settings object. There are a lot of possible options, but for now, we need only several of them. Core Core First of all, we need to specify property. It’s required setting for Odi application. As file in folder, which we choose for server-side code, we can use to set current directory. sources index.ts src __dirname property is also required. It binds the server on the specified port. port Now about the following part: socket: true,static: {root: join(__dirname, '../../assets'),prefix: '/assets'} We must enable sockets and set options for serving static files All files from the folder are available by URL with prefix. assets /assets 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: npm install socket.io react react-dom That’s all, Odi will automatically import it. As you can see, is used under the hood for real-time functionality. Also React packages is required for templates processing. socket.io 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 ( , ) 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. JS CSS Message and History will be pretty simple, only and fields. We can do it with a simple interface, as we are not going to use a database. Message username text export interface Message {username: string;text: string;} And history service @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, decorator was used for class to set is as a service component. Service is singleton in Dependency Injection Container. Now it can be injected into others application components. Service HistoryService Put all this code in file in folder. history.ts src/services Web Socket Create file in the directory with the following code. chat.socket.ts src/sockets 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 namespace with handler for event. If event is fired, all clients that connected to this namespace will be notified with event and message data. /chat message:send message:send message:new As you can notice decorator defines namespaces. Leading slash is not required. To set up method as the handler for certain event, use decorator, that accepts event name as the argument. Socket OnEvent Also, we injected using decorator. field of class will be initialized by Odi, so you don’t need to do anything additional. HistoryService Autowired history ChatSocket 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 "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. (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.In tsconfig.json ..."jsx": "react" Layout Let’s create our layout component that will be a wrapper for all pages. As was mentioned above, all templates will be in folder. layout.view.tsx views 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 library we can use CDN. So simply replace in the script tag with the following link socket.io-client path/to/socket.io 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 folder. assets Chat Components Actually, we need 3 components for our chat: Message representation Controls (Send button, message input, username input) Chat container I think we can put all these components in 1 file, chat.view.tsx 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 interface as props type for component. Message ChatMessage Let’s add chat controls. attribute was used for convenience, as we are going to use js on the client side without any libs or frameworks. id 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. 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 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 decorator and inherit the class. The decorator sets the component type, so the DI (dependency injection) container can detect what the class will be used for. @Controller IController 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 file extension. So, let’s create in folder. tsx render.controller.tsx src/controllers 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 into property. Also, the handler for path with 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). HistoryService history / Get Starting Application Now, we can start our application and see what we got. Let’s specify script in file: start package.json "scripts": {"start": "tsc && node build/src/index.js"} Running command compile our source code and run server entry file. npm start Let’s open the browser and check localhost:8080 As you can see, we have just empty chat without any functionality, as we did not specify the client into folder. index.js assets Client First of all, let’s get references for chat container and controls. 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 element. We need the function for creating elements that represent messages. container 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 namespace and add the event handler for event. When this event is fired, the message element will be appended to the container. chat message:new const socket = io('/chat');socket.on('message:new', message => {const messageElement = createMessage(message);container.appendChild(messageElement);}); And the last step, handler for our button. onclinck button.onclick = () => {socket.emit('massage:send', {text: messageInput.value,username: usernameInput.value}); messageInput.value = ""; } We are collecting data from inputs and sending it as event. Also, the message input text will be cleared after every send. message:send Now we can refresh the page, and see what we have got. After refreshing the page, we will have history our messaging. Sandbox You can check the source code and interact with the application right here: PS Thanks for the reading! If you like Odi, please support us with a simple start on GitHub Odi Also, if you are looking for more information, you can check previous articles and docs: Docs First Article Second Article If you have any ideas or questions, feel free to leave them! Thanks a lot! :)