Building and Deploying a Real-Time WebSocket Chat Application with Deno ๐Ÿฆ•, Oak ๐Ÿฟ๏ธ, and Render ๐Ÿš€

by rudr
Building and Deploying a Real-Time WebSocket Chat Application with Deno ๐Ÿฆ•, Oak ๐Ÿฟ๏ธ, and Render ๐Ÿš€
WebsocketRealtimeDeploymentJavascriptDeno

Have you ever wanted to build a real-time chat application but felt overwhelmed by the technical details? Or maybe youโ€™ve wondered how to deploy your app without emptying your wallet?

Well, youโ€™re in the right place! Imagine creating a WebSocket chat app from scratch, using the power of Deno ๐Ÿฆ• and Oak ๐Ÿฟ๏ธ, and then deploying it for free with Render ๐Ÿš€. Sounds pretty awesome, right?

In this article, Iโ€™m going to show you how to do just that. Weโ€™ll walk through setting up the Socket Deno ๐Ÿ”Œ๐Ÿฆ• template, running it locally, and deploying it so you can see your app live on the web โ€” all without spending a cent.


Why Deno and Oak? ๐Ÿฆ•๐Ÿฟ๏ธ

Honestly speaking, I wanted to try something new, and you should too! I stumbled upon Bun, Hono, and Cloudflare Workers in my journey, but they were a bit tricky for me to get a handle on.

Thatโ€™s when I discovered the Deno + Oak combo. Deno is a modern, secure runtime for JavaScript and TypeScript thatโ€™s super straightforward. Oak is a lightweight framework that makes building web apps a breeze. Pairing them up and deploying on Render? Itโ€™s been a game-changer for me.

Letโ€™s dive into why this stack is so awesome and how it can help you build your own real-time chat app with ease.


Check Out the Repo and Set Up Locally ๐Ÿ—‚๏ธ

Want to get your hands on the code? Head over to my GitHub repository for the Socket Deno ๐Ÿ”Œ๐Ÿฆ• template. The repo has everything you need to get started, including detailed setup instructions.


Understanding the Code ๐Ÿ”

WebSocket Setup ๐Ÿ› ๏ธ
  • connectedClients: Keeps track of all active WebSocket connections with usernames as keys.
  • broadcast(message): Sends a message to all connected clients.
  • broadcast_usernames(): Updates all clients with the current list of usernames.
Handling WebSocket Connections ๐ŸŒ
  • /start_web_socket Route: Upgrades the connection to WebSocket, manages client connections, and handles events like sending and receiving messages.
Connection Events
  • onopen: Updates all clients with new usernames when someone connects.
  • onclose: Removes clients and updates usernames when someone disconnects.
  • onmessage: Processes incoming messages and broadcasts them.
Serving Static Files ๐Ÿ“‚
  • Serves index.html from the public directory for the root URL.
Starting the Server ๐Ÿš€
  • Listens on port 8080 and logs the URL.

Deploying on Render ๐Ÿš€

Time to go live! Hereโ€™s how to deploy your WebSocket chat app on Render:

Push Your Code to GitHub ๐Ÿง‘โ€๐Ÿ’ป
  • Make sure your code is pushed to a GitHub repo. If you havenโ€™t done that yet, go ahead and upload it.
Set Up on Render ๐ŸŒ
  • Log in to Render and connect it to your GitHub account.
  • Follow the deployment instructions in the README file of my GitHub repo.

And thatโ€™s it! Render will handle the rest and provide you with a URL to your live app. Easy peasy!


Full Code โŒจ๏ธ

Server.js
import { Application, Router } from "@oak/oak";

const connectedClients = new Map();

const app = new Application();
const port = 8080;
const router = new Router();

function broadcast(message) {
  for (const client of connectedClients.values()) {
    try {
      client.send(message);
    } catch (error) {
      console.error("Error broadcasting message:", error);
    }
  }
}

function broadcast_usernames() {
  const usernames = [...connectedClients.keys()];
  console.log(
    "Sending updated username list to all clients: " + JSON.stringify(usernames)
  );
  broadcast(
    JSON.stringify({
      event: "update-users",
      usernames: usernames,
    })
  );
}

router.get("/start_web_socket", async (ctx) => {
  const socket = await ctx.upgrade();
  const username = ctx.request.url.searchParams.get("username");
  if (connectedClients.has(username)) {
    socket.close(1008, `Username ${username} is already taken`);
    return;
  }
  socket.username = username;
  connectedClients.set(username, socket);
  console.log(`New client connected: ${username}`);

  socket.onopen = () => {
    broadcast_usernames();
  };

  socket.onclose = () => {
    console.log(`Client ${socket.username} disconnected`);
    connectedClients.delete(socket.username);
    broadcast_usernames();
  };

  socket.onmessage = async (m) => {
    try {
      const data = JSON.parse(m.data);
      switch (data.event) {
        case "send-message":
          await broadcast(
            JSON.stringify({
              event: "send-message",
              username: socket.username,
              message: data.message,
            })
          );
          break;
        default:
          console.warn("Unknown event:", data.event);
      }
    } catch (error) {
      console.error("Error handling message:", error);
    }
  };

  socket.onerror = (error) => {
    console.error("WebSocket error:", error);
  };
});

app.use(router.routes());
app.use(router.allowedMethods());
app.use(async (context) => {
  await context.send({
    root: `${Deno.cwd()}/`,
    index: "public/index.html",
  });
});

console.log("Listening at http://localhost:" + port);
await app.listen({ port });

Contributing and License ๐Ÿค

Want to contribute? Great! If you spot any bugs or have ideas for improvements, feel free to:

  • Submit an Issue: Report any problems or feature requests on the GitHub Issues page.
  • Send a Pull Request: Got a fix or new feature? Submit a pull request with your changes, and Iโ€™ll review and merge them.
  • License ๐Ÿ“: This project is licensed under the MIT License.

Wrap-Up and Thanks! ๐ŸŽ‰

Thatโ€™s a wrap โ€” your WebSocket chat app is live! ๐Ÿš€ If you enjoyed this guide, hit the โญ๏ธ on my GitHub repo to show some love & support.

Feel free to tweak, experiment, and have fun with the code. If you need help or just want to chat, drop a comment or reach out.

Happy coding and see you around! ๐Ÿ‘‹๐Ÿฆ•