Deno WebSocket Realtime Chat App + TypeScript
Tags: deno, websocket, typescript
Published: 2021-08-15 17:58:03

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](http://fostlet.herokuapp.com). 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pkqhvrb3y824b7as9ovo.gif)

## Installation
![Deno Logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/etersl552t8v74ahfy73.png)
First you should install [Deno](https://deno.land/#installation) 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.
```html
<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>
```

Also, add some styles using a style tag inside the `index.html` file. More customizations are up to you.
```html
<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>
```
![First Look](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ll8po2nfi4qz6dyp4dwo.png)

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
```typescript
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');
```

Handle name form submission request
```typescript
initForm.addEventListener('submit', (e) => {
    e.preventDefault();
    name = initForm.name.value;
    msgRoom.style.display = "block"
    initForm.style.display = "none"
});
```
Handle message form submission request and send to the server-side in JSON format.
```typescript
msgForm.addEventListener('submit', e => {
    e.preventDefault();
    const msg = msgForm.msg.value;
    ws.send(JSON.stringify({
        name,
        msg
    }));
    msgForm.msg.value = '';
});
```

Output incoming message in the client side, with HTML elements.
```typescript
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;
}
```

Finally, add event listener for WebSocket.
```typescript
ws.addEventListener('message', msgOutput);
```

### 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
```typescript
import { WebSocket, isWebSocketCloseEvent } from "https://deno.land/std/ws/mod.ts";
import { v4 } from "https://deno.land/std/uuid/mod.ts";
```

Implement WebScoket
```typescript
let sockets = new Map<string, WebSocket>();
```

Create TypeScript interface for event broadcaster function and define the function as `eventBrodcaster `.
```typescript
interface BrodcastInterface {
    name: string,
    msg: string
};

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

Now create the connection to handle requests.
```typescript
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);
        }
    }
}
```

Export the connection at the very bottom of the file.
```typescript
export { connection };
```


### 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.
```typescript
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';
```

Setup server to listen port `3000` and add a `console.log` if you want.
```typescript
const server = serve({ port: 3000 });
console.log("Chat app listening on port 3000");
```

Listen to the browser routes of the root directory and WebSocket directory.
```typescript
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)
        }
    }
}
```

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

Now you can see your simple chat app live on the port number `3000`.
Follow me on [Twitter](https://twitter.com/ThirashaPw)

Happy Coding!🎉