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:
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.
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.
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.
Here’s what we’ll cover in this tutorial:
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:
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.
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.
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.
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
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowExplore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.
Optimize search parameter handling in React and Next.js with nuqs for SEO-friendly, shareable URLs and a better user experience.