Building Real-Time Applications with Durable Objects and WebSockets: A Friendly Guide
Author: Cristian González /
Introduction
Have you ever wanted to build a real-time application like a chat app or live game but worried about the technical complexity? Cloudflare’s Durable Objects paired with WebSockets offers a surprisingly accessible way to create persistent, real-time connections. In this article, we’ll explore how to use these technologies to build interactive applications without diving too deep into technical waters.
What are Durable Objects and WebSockets?
Durable Objects are like digital containers that:
- Remember information between user visits
- Live on Cloudflare’s global network
- Can handle thousands of simultaneous connections
- Automatically “sleep” when not in use (hibernation)
WebSockets create a two-way communication channel that:
- Stays open, unlike regular web requests
- Allows instant updates in both directions
- Works perfectly with modern browsers and mobile apps
Why This Combination Works So Well
When you pair Durable Objects with WebSockets, you get:
- Low latency - messages arrive in milliseconds, not seconds
- Cost efficiency - objects hibernate when not in use
- Global reach - your app works worldwide without complex setup
- Simplicity - no need to manage servers or databases
Building a Simple Chat Application
Let’s create a basic real-time chat that demonstrates these concepts:
1. The Server Side (Durable Object)
import { Env } from 'hono'
import { DurableObject } from 'cloudflare:workers'
export class ChatRoom extends DurableObject {
// Keep track of connected users
connections = new Map()
constructor(ctx: DurableObjectState, env: Env) {
// This is reset whenever the constructor runs because
// regular WebSockets do not survive Durable Object resets.
//
// WebSockets accepted via the Hibernation API can survive
// a certain type of eviction, but we will not cover that here.
super(ctx, env)
}
// Handle new connections
async fetch(request) {
// Create a WebSocket pair - one for the client, one for the server
const pair = new WebSocketPair()
const [client, server] = Object.values(pair)
// Accept the WebSocket connection with hibernation enabled
this.ctx.acceptWebSocket(server)
// Send back the client WebSocket
return new Response(null, {
status: 101,
webSocket: client
})
}
// When a new WebSocket connects
webSocketOpen(ws) {
// Store the connection
this.connections.set(ws, { name: 'Anonymous' })
// Send welcome message
ws.send(
JSON.stringify({
type: 'SYSTEM',
message: 'Welcome to the chat room!'
})
)
}
// When a message is received
webSocketMessage(ws, message) {
// Parse the message
const data = JSON.parse(message)
// Broadcast to all connections
for (const connection of this.connections.keys()) {
if (connection !== ws) {
// Don't send back to sender
connection.send(
JSON.stringify({
type: 'MESSAGE',
user: this.connections.get(ws).name,
message: data.message
})
)
}
}
}
// When a connection closes
webSocketClose(ws) {
// Remove the connection
this.connections.delete(ws)
}
}
2. The Client Side
// Connect to our chat room
async function connectToChat() {
const socket = new WebSocket('wss://your-worker.example.dev/chat')
// Handle incoming messages
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data)
// Add message to chat display
const chatBox = document.getElementById('chat-messages')
const messageEl = document.createElement('div')
if (data.type === 'SYSTEM') {
messageEl.className = 'system-message'
messageEl.textContent = data.message
} else {
messageEl.className = 'user-message'
messageEl.textContent = `${data.user}: ${data.message}`
}
chatBox.appendChild(messageEl)
})
return socket
}
// Send a message
function sendMessage(socket, message) {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ message }))
}
}
// Connect when page loads
let chatSocket
window.addEventListener('load', async () => {
chatSocket = await connectToChat()
// Set up the send button
document.getElementById('send-button').addEventListener('click', () => {
const input = document.getElementById('message-input')
sendMessage(chatSocket, input.value)
input.value = ''
})
})
Understanding Hibernation
One of the coolest features is hibernation, which works like this:
- When your chat room is active, the Durable Object is running
- If no one sends messages for a while, Cloudflare puts it to “sleep”
- When someone connects again, it “wakes up” automatically
- Your data is preserved, but you don’t pay for idle time
Hibernation happens automatically when you use this.ctx.acceptWebSocket(server)
, so you don’t need special code to enable it.
Benefits for Real Applications
- Dating apps: Match participants can chat without delay
- Multiplayer games: Game state updates instantly for all players
- Collaborative tools: See other people’s changes as they happen
- Live dashboards: Update metrics in real-time without page refreshes
Simple Deployment
To deploy your application:
# Install Wrangler (Cloudflare's CLI tool)
npm install -g wrangler
# Log in to Cloudflare
wrangler login
# Create a new project
wrangler generate my-realtime-app
# Deploy your code
wrangler publish
Conclusion
Building real-time applications used to require complex infrastructure and deep technical knowledge. With Durable Objects and WebSockets, you can create responsive, global applications with remarkably little code. The hibernation feature ensures you’re only paying for what you use, making this approach accessible even for small projects and startups.