March 6, 2026

PBX Science

VoIP & PBX, Networking, DIY, Computers.

How to Build a Real-Time Chat App on Cloudflare?

How to Build a Real-Time Chat App on Cloudflare?



Build a Real-Time Chat App on Cloudflare Workers
Cloudflare Workers · Durable Objects · WebSockets

Build a Real-Time
Chat App on Cloudflare

A complete, accurate guide — from environment setup to global deployment — using Workers, Durable Objects, and WebSockets.

Wrangler CLI WebSockets Durable Objects Node.js v20+ LTS
Introduction

How It All Fits Together

Cloudflare Workers give you a globally distributed JavaScript runtime that runs at the edge — no servers to manage. For a chat app, you need two Cloudflare primitives working in tandem:

Request Flow
Browser
Client A
WebSocket
──→
Cloudflare Worker
Edge Router
routes by room ID
──→
ChatRoom
Durable Object
single instance
──→
SQLite
Persistent Storage
message history
Browser
Client B
──→
same Worker
same room ID
──→
same ChatRoom
instance

Cloudflare Worker — receives HTTP/WebSocket requests and routes them to the correct Durable Object instance based on the room ID.

Durable Object (ChatRoom) — a stateful, single-instance object that keeps all WebSocket connections for a room in memory and broadcasts messages to every connected client. It also uses SQLite-backed storage for message history.

RateLimiter Durable Object — a lightweight companion object that throttles individual IP addresses to prevent spam.

Before You Begin

Prerequisites

  • A free Cloudflare account
  • Node.js v20 LTS or later installed
  • Git installed
  • Basic familiarity with JavaScript / terminal commands
  • Durable Objects are available on the Workers Paid plan ($5/month) — the free plan does not support them
Paid Plan Required Durable Objects require the Cloudflare Workers Paid plan. You cannot deploy this chat app on the free tier. Enable it in your Cloudflare dashboard under Workers & Pages → Plans.
Step 1

Install the Required Tools

A. Install Git

Git is needed to clone the official demo repository. Download it from git-scm.com/downloads and follow the installer for your OS.

bash · verify
git --version
# Expected output: git version 2.x.x

B. Install Node.js (v20 LTS recommended)

Download the LTS release from nodejs.org. Node v20+ is recommended as of 2025–2026; v18 works but is approaching end-of-life. The installer includes npm automatically.

bash · verify
node -v   # v20.x.x or higher
npm  -v   # 10.x.x or higher

C. Install the Wrangler CLI

Wrangler is Cloudflare’s official CLI for building, testing, and deploying Workers. Install it globally:

bash
npm install -g wrangler

# Verify
wrangler --version
# Expected: ⛅️ wrangler 3.x.x
💡
Global vs npx Once installed globally, use wrangler directly. If you prefer not to install globally, every Wrangler command can be prefixed with npx (e.g. npx wrangler dev), which runs the locally installed version from your project’s node_modules.

D. Authenticate Wrangler with your Cloudflare account

bash
wrangler login

This opens a browser window asking you to authorize Wrangler to access your Cloudflare account. Once approved, your credentials are stored locally and all subsequent deploys are tied to your account.

Step 2

Get the Project Code

The official Cloudflare Workers Chat Demo is the canonical starting point. Clone it and install dependencies:

bash
git clone https://github.com/cloudflare/workers-chat-demo.git
cd workers-chat-demo
npm install

After installation, your project directory will look like this:

workers-chat-demo/ ├── src/ │ ├── chat.mjs ← main Worker entry point │ └── index.html ← chat UI served by Worker ├── wrangler.toml ← Cloudflare config ├── package.json └── package-lock.json
Network issues? If cloning fails due to network restrictions, you can configure a local HTTP proxy for Git: git config --global http.proxy http://127.0.0.1:PORT — replacing PORT with your proxy port. Remove it afterward with git config --global --unset http.proxy.
Step 3

Understand the Configuration

The wrangler.toml file tells Cloudflare how to build and deploy your Worker. Here is what the config looks like and what each part does:

toml · wrangler.toml
name               = "edge-chat-demo"
compatibility_date = "2024-01-01"
main               = "src/chat.mjs"

# Bind Durable Object classes to variable names
[durable_objects]
bindings = [
  { name = "rooms",    class_name = "ChatRoom"    },
  { name = "limiters", class_name = "RateLimiter" },
]

# Serve the HTML file as a static asset
[[rules]]
type        = "Data"
globs       = ["**/*.html"]
fallthrough = false

# Register classes as Durable Objects with SQLite storage
[[migrations]]
tag               = "v1"
new_sqlite_classes = ["ChatRoom", "RateLimiter"]

Configuration breakdown

name — The name of your deployed Worker, visible in the Cloudflare dashboard.

compatibility_date — Pins the Worker to a specific behavior snapshot. Prevents future Cloudflare platform changes from silently breaking your code.

main — Entry point file. The Worker runtime executes this module first.

[durable_objects].bindings — Maps a variable name (e.g. env.rooms) to a Durable Object class (ChatRoom). Your Worker code uses env.rooms.get(id) to get a stub pointing at the correct instance.

new_sqlite_classes — Registers the listed classes as Durable Objects with SQLite-backed storage (the current recommended approach, replacing the older new_classes syntax).

Do not use new_classes Older tutorials use new_classes in the migrations block. The correct modern syntax is new_sqlite_classes, which provisions each Durable Object with its own SQLite database for persistent storage.
Step 4

Core Code Logic

The app has two layers: the Worker router (stateless, handles HTTP/WebSocket upgrades) and the ChatRoom Durable Object (stateful, manages connections and broadcasts).

Worker: routing requests to a room

javascript · src/chat.mjs (simplified)
export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // Extract the room name from the URL path, e.g. /room/general
    const roomName = url.pathname.slice(1) || "default";

    // Derive a stable Durable Object ID from the room name
    const id = env.rooms.idFromName(roomName);

    // Get a stub — a proxy to the Durable Object instance
    const room = env.rooms.get(id);

    // Forward the request to the ChatRoom Durable Object
    return room.fetch(request);
  }
};

ChatRoom Durable Object: managing WebSocket connections

javascript · src/chat.mjs (simplified)
export class ChatRoom {
  constructor(state, env) {
    this.state   = state;
    this.sessions = [];  // All active WebSocket connections in this room
  }

  async fetch(request) {
    // Upgrade HTTP → WebSocket
    const [client, server] = new WebSocketPair();
    server.accept();

    // Add this connection to the room's session list
    this.sessions.push(server);

    server.addEventListener("message", (event) => {
      // Broadcast every message to all other clients
      for (const session of this.sessions) {
        if (session !== server) session.send(event.data);
      }
    });

    server.addEventListener("close", () => {
      // Remove session on disconnect
      this.sessions = this.sessions.filter(s => s !== server);
    });

    return new Response(null, {
      status: 101,
      webSocket: client,
    });
  }
}
💡
Why Durable Objects for this? Each ChatRoom instance runs as a single process in one Cloudflare data center. This guarantees that all WebSocket connections for a given room share the same in-memory session list — something that would require an external pub/sub system (Redis, etc.) in a traditional multi-server setup.

RateLimiter Durable Object

The RateLimiter class is a simple companion object that tracks request counts per IP. Each IP maps to its own RateLimiter instance. If a client sends messages too quickly, the ChatRoom asks the RateLimiter and rejects the message if the limit is exceeded. This prevents any single user from flooding the room.

Step 5

Local Development & Testing

Before touching production, run the app locally with wrangler dev. This starts a local HTTP server that simulates the Cloudflare Workers runtime, including Durable Object support:

bash
wrangler dev

# Output will include something like:
# ⛅️  wrangler 3.x.x
# -------------------------------------------------------
# wrangler dev now uses local mode by default.
# [mf:inf] Ready on http://127.0.0.1:8787

Open http://127.0.0.1:8787 in two browser tabs to simulate two chat participants. Messages sent in one tab should appear in the other in real time.

Local Durable Objects Wrangler v3 runs Durable Objects locally in a Miniflare-based sandbox. State is ephemeral — it resets every time you restart wrangler dev. This is expected behavior for local testing.
Step 6

Deploy to Cloudflare’s Global Network

When you’re happy with local testing, one command deploys your Worker to Cloudflare’s edge — available globally within seconds:

bash
wrangler deploy

# Successful output looks like:
# Uploaded edge-chat-demo (1.23 sec)
# Published edge-chat-demo (0.45 sec)
# https://edge-chat-demo.YOUR-SUBDOMAIN.workers.dev

Cloudflare will print a live URL — something like https://edge-chat-demo.your-subdomain.workers.dev. Open it in multiple browsers or share it with anyone; your chat room is live.

What happens on deploy?

Wrangler bundles your Worker code, registers the Durable Object classes via the migrations you defined, and distributes everything across Cloudflare’s network. The first time a room is accessed after deployment, Cloudflare automatically allocates a Durable Object instance for it.

💡
Custom domain You can attach a custom domain to your Worker in the Cloudflare dashboard under Workers & Pages → your Worker → Settings → Domains & Routes. No separate DNS setup is needed if your domain’s nameservers are already pointing to Cloudflare.
Reference

Accuracy & Common Pitfalls

Some older articles and tutorials contain outdated information. Here is a quick reference of what is current:

comparison · old vs current
❌  Old: new_classes = ["ChatRoom", "RateLimiter"]
✅  Now: new_sqlite_classes = ["ChatRoom", "RateLimiter"]

❌  Old: Node.js v18 recommended
✅  Now: Node.js v20 LTS (v18 is near end-of-life)

❌  Old: npm install -g wrangler, then use npx wrangler (inconsistent)
✅  Now: pick one — global install → use wrangler
              OR no global install → always use npx wrangler

❌  Old: Durable Objects available on free plan
✅  Now: Durable Objects require the Workers Paid plan ($5/mo)
  • Always use new_sqlite_classes in migrations, not new_classes
  • Node.js v20+ LTS is the current recommended version
  • Durable Objects are not available on the free Workers tier
  • Be consistent: either install Wrangler globally or always use npx
  • The compatibility_date field is mandatory — omitting it causes a deployment error
  • Local state in wrangler dev is ephemeral and resets on restart

How to Build a Real-Time Chat App on Cloudflare?

How to Build a Real-Time Chat App on Cloudflare?


Windows Software Alternatives in Linux


Disclaimer of pbxscience.com

PBXscience.com © All Copyrights Reserved. | Newsphere by AF themes.