Build realtime apps on Cloudflare with Socket.io-like simplicity
Getting Started • Documentation • Examples
Verani brings the familiar developer experience of Socket.io to Cloudflare's Durable Objects (Actors), with proper hibernation support and minimal overhead. Build realtime chat, presence systems, notifications, and more—all running on Cloudflare's edge.
- Familiar API: If you've used Socket.io, you already know how to use Verani
- Horizontally scalable: Per-connection architecture - each user gets their own Durable Object
- Hibernation support: Handles Cloudflare Actor hibernation automatically
- Type safe: Built with TypeScript, full type safety throughout
- Simple mental model: Connections, rooms, and broadcast semantics that just make sense
- Modern DX: Automatic reconnection, error handling, and connection lifecycle management
- Edge-ready: Built for Cloudflare Workers and Durable Objects
- Cost-efficient: Idle connections hibernate and cost nothing
Get a realtime chat app running in 5 minutes.
npm install verani @cloudflare/actorsDon't have a Cloudflare Worker project? Create one:
npm create cloudflare@latest my-verani-app
cd my-verani-app
npm install verani @cloudflare/actorsCreate src/actors/connection.ts:
import { defineConnection, createConnectionHandler, createRoomHandler } from "verani";
// Define connection handler (one WebSocket per user)
const userConnection = defineConnection({
name: "UserConnection",
extractMeta(req) {
const url = new URL(req.url);
const userId = url.searchParams.get("userId") || crypto.randomUUID();
return {
userId,
clientId: crypto.randomUUID(),
channels: ["default"]
};
},
async onConnect(ctx) {
console.log(`User ${ctx.meta.userId} connected`);
// Join chat room (persisted across hibernation)
await ctx.actor.joinRoom("chat");
},
async onDisconnect(ctx) {
console.log(`User ${ctx.meta.userId} disconnected`);
// Room leave is handled automatically
}
});
// Handle messages (socket.io-like API)
userConnection.on("chat.message", async (ctx, data) => {
// Broadcast to everyone in the chat room
await ctx.emit.toRoom("chat").emit("chat.message", {
from: ctx.meta.userId,
text: data.text,
timestamp: Date.now()
});
});
// Export the connection handler
export const UserConnection = createConnectionHandler(userConnection);
// Export the room coordinator
export const ChatRoom = createRoomHandler({ name: "ChatRoom" });Update src/index.ts:
import { UserConnection, ChatRoom } from "./actors/connection";
// Export Durable Object classes
export { UserConnection, ChatRoom };
// Route WebSocket connections
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url);
if (url.pathname.startsWith("/ws")) {
// Extract userId and route to user-specific DO
const userId = url.searchParams.get("userId") || crypto.randomUUID();
const stub = UserConnection.get(userId);
return stub.fetch(request);
}
return new Response("Not Found", { status: 404 });
}
};Update wrangler.jsonc:
Important: Export names must match class_name in wrangler.jsonc.
import { VeraniClient } from "verani/client";
const client = new VeraniClient("ws://localhost:8787/ws?userId=alice");
// Listen for messages
client.on("chat.message", (data) => {
console.log(`${data.from}: ${data.text}`);
});
client.on("user.joined", (data) => {
console.log(`User ${data.userId} joined!`);
});
// Send messages
client.emit("chat.message", { text: "Hello, world!" });
// Wait for connection (optional)
await client.waitForConnection();# Start the server
npm run dev
# or
wrangler dev
# In another terminal, run your client
# (or open multiple browser tabs with your client code)That's it! You now have a working realtime chat app.
Need more help? Check out the Quick Start Guide for detailed examples.
- Live Documentation - Installation and quick start guide
- Getting Started - Installation and quick start guide
- API Reference - Complete server and client API documentation
- Guides - Configuration, deployment, scaling, and RPC
- Examples - Common usage patterns and code samples
- Concepts - Architecture, hibernation, and core concepts
- Security - Authentication, authorization, and best practices
- ConnectionDO = A Durable Object that owns ONE WebSocket per user
- RoomDO = A Durable Object that coordinates room membership and broadcasts
- Emit = Send messages (
ctx.emit.toRoom("chat").emit("event", data)) - Hibernation = Handled automatically, state persisted and restored
- Per-connection DOs: Each user gets their own Durable Object (horizontally scalable)
- Socket.io-like API:
connection.on(),ctx.emit.toRoom(), familiar patterns - Room coordination: RoomDOs manage membership and broadcast via RPC
- Lifecycle hooks:
onConnect,onDisconnect,onMessagefor full control - RPC support: DO-to-DO communication for message delivery
- Automatic hibernation: State persisted and restored automatically
- Persistent state: Built-in support for state that survives hibernation
- Type safety: Full TypeScript support with type inference
- Automatic reconnection: Exponential backoff with configurable retry logic
- Message queueing: Messages queued when disconnected, sent on reconnect
- Keepalive: Built-in ping/pong to detect dead connections
- Event-based API: Familiar
on(),emit(),once(),off()methods - Connection state: Track connection lifecycle (
connecting,connected,disconnected)
See Verani in action with working examples:
git clone https://github.com/v0id-user/verani
cd verani
bun install && bun run devThen in another terminal, try:
# Chat room example
bun run examples/clients/chat-client.ts
# Presence tracking
bun run examples/clients/presence-client.ts
# Notifications feed
bun run examples/clients/notifications-client.tsSee the Examples README for more details.
Vchats - A complete chat application built with Verani
ISC
Contributions welcome! Please read our Contributing Guidelines first.
{ "name": "my-verani-app", "main": "src/index.ts", "compatibility_date": "2025-11-26", "durable_objects": { "bindings": [ { "class_name": "UserConnection", "name": "CONNECTION_DO" }, { "class_name": "ChatRoom", "name": "ROOM_DO" } ] }, "migrations": [ { "new_sqlite_classes": ["UserConnection", "ChatRoom"], "tag": "v1" } ] }