Deno WebSocket Realtime Chat App + TypeScript

thirashapraween

Thirasha Praween

Posted on August 15, 2021

Deno WebSocket Realtime Chat App + TypeScript

One of the things that interested me when I was learning Deno is creating a real-time chat application. but when I was learning NodeJs with WebSocket, I've created and deployed a chat application called fostlet. You can go and try it with different chat rooms.

In this post, I'll show you how to build a simple chat application with Deno using TypeScript. Deno supports both JavaScript and TypeScript as first-class languages at runtime. This means it requires fully qualified module names, including the extension (or a server providing the correct media type). Typescript modules can be directly imported. So I think TypeScript is better for Deno.

Complete chat app

Installation

Deno Logo
First you should install Deno on your computer if you haven't. Basically, you can install the setup by running a simple command.

Usage

We'll develop this chat app with WebSocket. WebSocket is a library that allows us to do real-time, bidirectional, and event-based communication between the browser and the server.

Step 1

Create a new file called index.html in a new folder and write the code below to create a simple user interface.

<div class="container">
  <form class="init_form">
    <input type="text" name="name" placeholder="Enter your name" required />
    <button>Start Chat</button>
  </form>

  <div class="msg_room">
    <ul class="msg_list"></ul>
    <form class="msg_form">
      <input type="text" name="msg" placeholder="Type a message" required />
      <button>Send</button>
    </form>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Also, add some styles using a style tag inside the index.html file. More customizations are up to you.

<style>

    body{
        font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
    }
    .container{
        width: 100vw;
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
    }
    input[type=text]{
        padding: 5px;
        border-radius: 5px;
        border: #888 2px solid;
        width: 250px;
    }
    button{
        padding: 5px;
        background-color: #eee;
        border-radius: 5px;
        outline: none;
    }
    .msg_room{
        display: none;
    }
    .pname{
        font-weight: bold;
        margin-left: 5px;
    }
    .pmsg{
        background-color: #eee;
        border-radius: 10px;
        padding: 10px;
        margin-bottom: 10px;
    }
    ul{
        list-style: none;
    }

</style>
Enter fullscreen mode Exit fullscreen mode

First Look

Okay, let's do JavaScript, inside index.html create a new <script> tag to write JS for handle message submit, username submit, and WebSocket implementation on the client-side.

Select Dom elements and WebSocket

const initForm = document.querySelector('.init_form');
const msgRoom = document.querySelector('.msg_room');
const msgList = document.querySelector('.msg_list');
const msgForm = document.querySelector('.msg_form');
const ws = new WebSocket('ws://localhost:3000/ws');
Enter fullscreen mode Exit fullscreen mode

Handle name form submission request

initForm.addEventListener('submit', (e) => {
    e.preventDefault();
    name = initForm.name.value;
    msgRoom.style.display = "block"
    initForm.style.display = "none"
});
Enter fullscreen mode Exit fullscreen mode

Handle message form submission request and send to the server-side in JSON format.

msgForm.addEventListener('submit', e => {
    e.preventDefault();
    const msg = msgForm.msg.value;
    ws.send(JSON.stringify({
        name,
        msg
    }));
    msgForm.msg.value = '';
});
Enter fullscreen mode Exit fullscreen mode

Output incoming message in the client side, with HTML elements.

const msgOutput = ({ data }) => {
    const { name, msg } = JSON.parse(data);
    const li = `
        <li>
            <div class="pname">${name}</div>
            <div class="pmsg">${msg}</div>
        </li>
    `;
    msgList.innerHTML += li;
}
Enter fullscreen mode Exit fullscreen mode

Finally, add event listener for WebSocket.

ws.addEventListener('message', msgOutput);
Enter fullscreen mode Exit fullscreen mode

Step 2

Alright! Now client side is done. The next move is to create a server-side connection. Create a new file connection.ts at the same as the index.html directory.

Import WebSocket and random user id generating library

import { WebSocket, isWebSocketCloseEvent } from "https://deno.land/std/ws/mod.ts";
import { v4 } from "https://deno.land/std/uuid/mod.ts";
Enter fullscreen mode Exit fullscreen mode

Implement WebScoket

let sockets = new Map<string, WebSocket>();
Enter fullscreen mode Exit fullscreen mode

Create TypeScript interface for event broadcaster function and define the function as eventBrodcaster.

interface BrodcastInterface {
    name: string,
    msg: string
};

const eventBrodcaster = (obj: BrodcastInterface) => {
    sockets.forEach((ws: WebSocket) => {
        ws.send(JSON.stringify(obj));
    });
}
Enter fullscreen mode Exit fullscreen mode

Now create the connection to handle requests.

const connection = async (ws: WebSocket) => {

    // New websocket and generate new user id
    const uid = v4.generate();
    sockets.set(uid, ws);

    for await (const ev of ws){

        // remove socket if user close the tab or browser
        if(isWebSocketCloseEvent(ev)){
            sockets.delete(uid);
        }

        // broadcast the message that user sent
        if(typeof ev === 'string'){
            let evObj = JSON.parse(ev);
            eventBrodcaster(evObj);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Export the connection at the very bottom of the file.

export { connection };
Enter fullscreen mode Exit fullscreen mode

Step 3

Okay, This is the final step, create new file server.ts at the same as index.html and connection.ts directory.

Import serve, WebSocket libraries and the connection.ts file.

import { serve } from "https://deno.land/std/http/server.ts";
import { acceptWebSocket, acceptable } from "https://deno.land/std/ws/mod.ts";
import { connection } from './connection.ts';
Enter fullscreen mode Exit fullscreen mode

Setup server to listen port 3000 and add a console.log if you want.

const server = serve({ port: 3000 });
console.log("Chat app listening on port 3000");
Enter fullscreen mode Exit fullscreen mode

Listen to the browser routes of the root directory and WebSocket directory.

for await (const req of server){
    // serve index.html file - route /
    if(req.url === '/'){
        req.respond({
            status: 200,
            body: await Deno.open('./index.html')
        });
    }

    // serve websocket route and accept socket - route /ws
    if(req.url === '/ws'){
        if(acceptable(req)){
            acceptWebSocket({
                conn: req.conn,
                bufReader: req.r,
                bufWriter: req.w,
                headers:req.headers
            })
            .then(connection)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now all done!. If you have already installed Deno, run this command in the terminal at the project directory.

--allow-net for allow network access. --allow-read for read files.

deno run --allow-net --allow-read server.ts
Enter fullscreen mode Exit fullscreen mode

Now you can see your simple chat app live on the port number 3000.
Follow me on Twitter

Happy Coding!🎉

💖 💪 🙅 🚩
thirashapraween
Thirasha Praween

Posted on August 15, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related