Deno WebSocket Realtime Chat App + TypeScript
Thirasha Praween
Posted on August 15, 2021
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.
Installation
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>
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>
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');
Handle name form submission request
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.
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.
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.
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
import { WebSocket, isWebSocketCloseEvent } from "https://deno.land/std/ws/mod.ts";
import { v4 } from "https://deno.land/std/uuid/mod.ts";
Implement WebScoket
let sockets = new Map<string, WebSocket>();
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));
});
}
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);
}
}
}
Export the connection at the very bottom of the file.
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.
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.
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.
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
Happy Coding!🎉
Posted on August 15, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.