Chisimdiri Ejinkeonye Fullstack developer.

Using WebSockets with Deno

5 min read 1507

Using Websockets Deno

WebSockets is a communications protocol that allows bidirectional, persistent communication as an alternative to HTTP. Suppose you wanted to get updates to some server state. With HTTP, you would need to poll frequently for the data.

But there are limitations to HTTP polling:

  1. Changes to data aren’t seen in real time by subscribing clients
  2. The server only responds to an initial request from the client — in other words, it’s unidirectional
  3. Server resources are tied up processing requests even when there’s no new data

With WebSockets, clients don’t need to ask the server for new data, the web sockets server simply pushes the new data directly to the client.

When should you use WebSockets?

WebSockets should be used when you need real-time functionality in your application — for example, a chat application or a bot that watches the stock market. WebSockets are best used in situations where you need to react quickly to frequently changing data. If the your application data doesn’t change often, it may be best to implement simple polling logic instead.

How does the WebSockets protocol work?

WebSockets use HTTP as the mechanism to initiate a connection to the server. This connection is then upgraded to a WebSocket connection.

Clients can only access WebSocket servers through a URI scheme of either ws:// or wss://. To initiate a WebSockets connection, you must first implement a WebSocket client and have a supporting WebSockets server. That’s where Deno comes in.

Implementing a WebSocket server and client with Deno

Here’s what we’ll cover in this tutorial:

  1. Creating a WebSocket server in Deno
  2. Creating a WebSocket client in Deno
  3. Sending messages between the server and client
  4. Broadcasting messages from the server to multiple clients

1. Creating a WebSocket server in Deno

First, we’ll need Deno installed on our local machines. Once that’s done, create a new directory to house both our server and client code:

mkdir websocket-tutorial

Create a new file called server.ts. This is where we’ll create a simple HTTP server.

touch ./server.ts

Next, at the top of the file, import the serve function:

We made a custom demo for .
No really. Click here to check it out.

import { serve } from "https://deno.land/[email protected]/http/server.ts";

Now let’s create the HTTP server on port 80, which will be used to bootstrap, accept, and send WebSocket connections:

for await (const req of serve({port:80})){
req.response({body:"Hello world"})
}

serve() returns an async iterable consisting of HTTP requests made to our server. The for await syntax is used to loop through and respond to each request.

Run the code with the following command:

deno run --allow-net server.ts

The --allow-net parameter gives Deno permission to make network calls. Type localhost:80 in your browser and you should see a “Hello world” message on the page.

Next, we need to upgrade the regular HTTP request to a WebSocket connection. Import the acceptWebSocket function from Deno’s WebSocket module:

import {acceptWebSocket} from "https://deno.land/[email protected]/ws/mod.ts"

acceptWebSocket takes care of upgrading the request to a WebSocket connection. We’ll provide it with the necessary parameters:

for await (const req of serve({ port: 80 })) {
    const { conn, r: bufReader, w: bufWriter, headers } = req;
         acceptWebSocket({
            conn,
            bufReader,
            bufWriter,
            headers,
        }).then(handleWs)
}

We haven’t created the handleWs function yet, so let’s do that before we move on:

async function handleWs(sock:WebSocket){
console.log("socket connected")
for await (const event of sock){
if(typeof event === "string"{
console.log(ev)
}
}
}

The handleWs function takes a WebSocket object as parameter. This object is an async iterable consisting of events emitted by the WebSocket-connected client. If the event is a string, the event is the message payload from the WebSocket client.

There are other kinds of events, which we’ll cover later. WebSockets can only transmit messages as an ArrayBuffer or a string, so you’ll need to use JSON.stringify for transmitting JSON messages.

With that done, let’s create the WebSocket client.

2. Creating a WebSocket client in Deno

Create a new file in the project folder named client.ts:

touch ./client.ts

We’ll define a function called createWebSocket, which will contain the code for initializing a WebSocket and sending and receiving WebSocket messages.

function createWebSocket() {
    const websocket = new WebSocket("ws://localhost:80")
    websocket.onopen = () => {
        setInterval(() => {
            websocket.send(`Client says hello`)
        }, 2000)
    }

}

Websocket URLs start with wss:// for secure connections or ws:// for unsecure connections.

When a WebSocket object is newly created, its connection isn’t immediately ready. Harnessing WebSockets’ event-driven nature. we can attach a function to the WebSocket.onopen event listener. This function is called once the WebSocket connection is open.

Inside the onopen event listener function, we use the setInterval function to send a message every two seconds.

Let’s test out our code by starting up the WebSocket server:

deno run --allow-net server.ts

And the client:

deno run --allow-net client.ts

We should see “Client says hello” printed on the server console every two seconds.

3. Sending messages between the server and client

We’ve seen how to send messages from the client to the server. But as we noted above, WebSockets allow bidirectional messaging. Let’s now send messages from the server to the client.

Update the handleWs function in server.ts:

async function handleWs(sock: WebSocket) {
    if (!sock.isClosed) {
        sock.send("Hi from server")
    } //add this

    for await (const ev of sock) {
        if (typeof ev === "string") {
            console.log(ev);
        }
    }
}

Notice that there’s a check to detect whether the socket has already been closed using the sock.isClosed property.

Update the createWebSocket function in client.ts to receive messages from the server:

function createWebSocket() {
    const websocket = new WebSocket("ws://localhost:80")
    websocket.onopen = () => {
        setInterval(() => {
            websocket.send(`Client says hello`)
        }, 2000)
    }
    websocket.onmessage = (message) => {
        console.log(message.data)
    }
}

Running the updated code should show “Hi from server” on the client’s console.

The onmessage event listener function is used to subscribe to messages sent from the WebSocket server. Its payload is within the data property.

4. Broadcasting messages from the server to multiple clients

We’ve seen how to send messages from the server to a single client, but this is rarely useful; in a chat application, for example, you would need to broadcast a message to multiple clients instantly. You might also need to differentiate one WebSocket connection from another.

At the top of our server.ts file, we’ll create a Map object and assign to it the variable sockets:

const sockets = new Map<string, WebSocket>()

A Map is an object with methods that wrap a key-value pair. In this case, we associate a string to a WebSockets object. We will store all WebSockets connections in the sockets variable.

Now update the handleWs function:

async function handleWs(sock: WebSocket) {
    console.log('connected')
    const uid = v4.generate()
    sockets.set(uid, sock)

    for await (const ev of sock) {
        if (isWebSocketCloseEvent(ev)) {
            sockets.delete(uid)
            return
        }
        if (typeof ev === "string") {
            console.log(ev)
            broadcastMessage(ev,uid)
        }
    }
}

Import the v4.generate function from the uuid library:

import { v4 } from 'https://deno.land/std/uuid/mod.ts';

The v4.generate function generates a random ID for each WebSocket connection. Each ID is used to identify a WebSocket connection when it sends a message. We’ll add this connection to the sockets variable.

Notice that we remove WebSocket connections once the close event occurs:

if (isWebSocketCloseEvent(ev)) {
 sockets.delete(uid)
 return
}

Next, we’ll create a function called broadcastMessage, which takes a message as a parameter for transmission to all the WebSockets stored in the sockets variable:

function broadcastMessage(message: string, uid: string) {
    sockets.forEach((socket) => {
        if (!socket.isClosed && uid !== id)
            socket.send(message)
    })
}

Notice we use a second parameter, uid, to identify the client sending the message. Notice the check uid !==id, which ensures we don’t broadcast a message to its source socket.

Now let’s update our client.ts file to simulate multiple clients connecting and sending messages. Update the createWebsocket function to take an id parameter:

function createWebSocket(id: number) {
    const websocket = new WebSocket("ws://localhost:80")
    websocket.onopen = () => {
        setInterval(() => {
            websocket.send(`Client ${id} says hello`)
        }, 2000 * id)
    }
    websocket.onmessage = (message) => {
        console.log(`Client ${id}: ${message.data}`)
    }
}

Notice the setInterval argument value 2000 * id. Since we’re passing numbers as IDs, an ID of 1 would wait 2,000ms (i.e., 2s) to send a message, whereas an ID of 4 would wait 8,000ms (i.e., 8s).

We create multiple WebSocket connections using a for loop:

for (let x = 1; x < 10; x++) {
    createWebSocket(x)
}

When we run both server.ts and client.ts, we should see messages like this:

Client 3: Client 8 says hello 
Client 4: Client 8 says hello 
Client 6: Client 8 says hello 

Conclusion

The WebSockets protocol provides a way to communicate bidirectionally without polling. WebSockets should be used for real-time applications like stock market visualization and messaging apps that require instant feedback.

Using WebSockets for applications with data that doesn’t change often would be overkill, and as such, a simple HTTP polling mechanism would be preferable. The full code for this tutorial can be found in the repo here.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Chisimdiri Ejinkeonye Fullstack developer.

Leave a Reply