Building real-time applications has always been one of the most exciting yet challenging areas of modern web development. Traditionally, WebSockets required dedicated servers, persistent connections, and careful scaling strategies to handle thousands—or even millions—of concurrent users. Thankfully, with the rise of serverless platforms, we can now achieve real-time communication without maintaining traditional server infrastructure.

In this article, we’ll explore how to build serverless WebSockets using Cloudflare Workers, Hono, and Durable Objects. We will walk step by step through the architecture, setup, and implementation with working code examples. By the end, you’ll have a clear roadmap for creating scalable and resilient real-time applications, such as live chat, collaborative editors, multiplayer games, or dashboards.

Understanding The Serverless WebSocket Architecture

Before diving into the implementation, let’s break down the major components we’ll be working with:

  • Cloudflare Workers: A serverless platform that runs lightweight JavaScript code at the edge. Workers handle WebSocket upgrades and routing without servers.

  • Hono Framework: A fast, minimal web framework for Cloudflare Workers (and other runtimes). It helps simplify routing, middleware, and request handling.

  • Durable Objects: A unique feature of Cloudflare Workers that provides strongly consistent state and coordination across multiple connections. This is where WebSocket sessions can be stored, managed, and synchronized.

The key challenge with WebSockets is state management. Since Cloudflare Workers are stateless and ephemeral, they cannot maintain persistent connections alone. This is why Durable Objects are critical—they allow multiple connections to be coordinated across the distributed edge network.

Use Cases For Serverless WebSockets

Before coding, let’s imagine a few real-world use cases:

  1. Chat Applications – Users can join a room, send messages, and see real-time updates.

  2. Collaboration Tools – Like Google Docs-style editors with live cursors and updates.

  3. Gaming – Multiplayer turn-based or real-time games needing synchronization.

  4. Real-Time Dashboards – Monitoring stock prices, IoT devices, or logs.

In all these scenarios, state consistency (ensured by Durable Objects) and scalability (handled by Cloudflare Workers) are essential.

Setting Up The Project

Let’s begin by setting up a Cloudflare Worker project using Hono and Durable Objects.

  1. Install Wrangler CLI (Cloudflare’s developer tool):

    npm install -g wrangler
  2. Initialize A New Worker Project:

    wrangler init websocket-demo
    cd websocket-demo
  3. Install Hono:

    npm install hono
  4. Configure wrangler.toml for Durable Objects:

    name = "websocket-demo"
    main = "src/index.ts"
    compatibility_date = "2023-10-01"
    [durable_objects]
    bindings = [
    { name = “CHAT_ROOM”, class_name = “ChatRoom” }
    ][[migrations]]
    tag = “v1”
    new_classes = [“ChatRoom”]

Here, CHAT_ROOM will be our Durable Object responsible for managing WebSocket connections in a given chat room.

Creating The Durable Object

A Durable Object represents a single instance of stateful logic. It can store active WebSocket sessions, broadcast messages, and ensure consistency.

Let’s implement the ChatRoom Durable Object:

export class ChatRoom {
state: DurableObjectState;
sessions: WebSocket[] = [];
constructor(state: DurableObjectState) {
this.state = state;
}// Handle WebSocket connections
async fetch(request: Request) {
if (request.headers.get(“Upgrade”) === “websocket”) {
const [client, server] = Object.values(new WebSocketPair());
await this.handleSession(server);
return new Response(null, { status: 101, webSocket: client });
}return new Response(“Expected WebSocket”, { status: 400 });
}async handleSession(socket: WebSocket) {
socket.accept();
this.sessions.push(socket);socket.addEventListener(“message”, (event) => {
this.broadcast(event.data);
});socket.addEventListener(“close”, () => {
this.sessions = this.sessions.filter((s) => s !== socket);
});
}broadcast(message: string) {
for (const socket of this.sessions) {
try {
socket.send(message);
} catch {
// Ignore broken connections
}
}
}
}

This Durable Object maintains:

  • An array of active WebSocket connections.

  • A broadcast() method to send messages to all connected clients.

  • A fetch() method that upgrades HTTP requests into WebSocket connections.

Connecting Hono With Durable Objects

Now let’s integrate Hono to route requests and connect users to the right Durable Object.

import { Hono } from "hono";

export interface Env {
CHAT_ROOM: DurableObjectNamespace;
}

const app = new Hono<{ Bindings: Env }>();

app.get(“/”, (c) => c.text(“Serverless WebSocket with Hono + Durable Objects”));

// Route to connect to a chat room
app.get(“/chat/:room”, async (c) => {
const roomName = c.req.param(“room”);
const id = c.env.CHAT_ROOM.idFromName(roomName);
const room = c.env.CHAT_ROOM.get(id);
const response = await room.fetch(c.req.raw);
return response;
});

export default app;

This code allows clients to connect to different chat rooms via /chat/roomName. Each room corresponds to a unique Durable Object instance identified by its name.

Client-Side WebSocket Example

On the frontend, connecting to our WebSocket server is straightforward. Let’s create a simple HTML + JavaScript client:

<!DOCTYPE html>
<html>
<body>
<h2>Chat Room</h2>
<input id="msg" placeholder="Type a message..." />
<button onclick="sendMessage()">Send</button>
<ul id="messages"></ul>
<script>
const room = “general”;
const ws = new WebSocket(`wss://<your-worker-domain>/chat/${room}`);ws.onmessage = (event) => {
const li = document.createElement(“li”);
li.textContent = event.data;
document.getElementById(“messages”).appendChild(li);
};function sendMessage() {
const input = document.getElementById(“msg”);
ws.send(input.value);
input.value = “”;
}
</script>
</body>
</html>

When multiple users connect to the same room, messages are broadcast to everyone in real time.

Scaling WebSockets Across The Edge

One of the biggest strengths of using Cloudflare Workers with Durable Objects is scalability. Unlike traditional servers, where sticky sessions or load balancers are required, Durable Objects automatically:

  • Ensure a single instance per chat room across the global network.

  • Route all connections for that room to the same Durable Object.

  • Handle reconnections and resilience automatically.

This design is far more efficient and cost-effective than maintaining fleets of WebSocket servers.

Adding Persistence With Durable Objects Storage

Right now, our chat app only broadcasts messages in memory. If the Durable Object restarts, all history is lost. To fix this, we can use this.state.storage for persistence:

async handleSession(socket: WebSocket) {
socket.accept();
this.sessions.push(socket);
// Load last 10 messages
const history = await this.state.storage.get<string[]>(“history”) || [];
for (const msg of history) socket.send(`[History] ${msg}`);socket.addEventListener(“message”, async (event) => {
const message = event.data;
await this.saveMessage(message);
this.broadcast(message);
});socket.addEventListener(“close”, () => {
this.sessions = this.sessions.filter((s) => s !== socket);
});
}async saveMessage(message: string) {
let history = await this.state.storage.get<string[]>(“history”) || [];
history.push(message);
if (history.length > 10) history.shift(); // Keep last 10
await this.state.storage.put(“history”, history);
}

This ensures users joining the chat see the latest conversation history.

Enhancing With Hono Middleware

Hono also supports middleware, which means we can add features like authentication or logging before connections are established. For example, requiring a query parameter token:

app.use("/chat/*", async (c, next) => {
const token = c.req.query("token");
if (token !== "secret123") {
return c.text("Unauthorized", 401);
}
await next();
});

This small change adds security without complicating the Durable Object logic.

Testing The Setup

You can run the project locally with:

wrangler dev

Then open the HTML client, connect to your worker, and open multiple browser tabs. Sending messages in one tab should immediately appear in all others.

When ready, deploy to Cloudflare’s edge:

wrangler publish

Benefits Of This Approach

By combining Cloudflare Workers, Hono, and Durable Objects, we achieve:

  • Serverless Scalability – No servers, just code running at the edge.

  • Global Low Latency – Workers run close to users worldwide.

  • Stateful Connections – Durable Objects provide persistent, consistent state.

  • Simple Development – Hono simplifies routing and middleware.

  • Cost Efficiency – Pay only for what you use, without idle servers.

This makes it a great solution for startups, hobby projects, and even enterprise-scale applications.

Conclusion

Building real-time applications used to require heavyweight infrastructure: dedicated servers, scaling strategies, and expensive maintenance. With Cloudflare Workers, Hono, and Durable Objects, that complexity is eliminated.

We’ve seen how Durable Objects solve the challenge of stateful WebSocket management by ensuring one consistent instance per room, enabling features like broadcasting and persistence with ease. Hono provides a lightweight and elegant routing layer, while Workers handle the scaling and performance globally.

The combination of these tools allows developers to create powerful real-time systems without servers—just pure serverless logic running at the edge. Whether you’re building a live chat, multiplayer game, or collaborative editor, this architecture gives you scalability, resilience, and simplicity.

In essence, the future of WebSockets is serverless and global. By adopting Cloudflare’s ecosystem with Hono and Durable Objects, you are embracing a development model that scales seamlessly, reduces complexity, and delivers excellent user experiences worldwide.