How Feakin Builds Real‑Time Collaborative Editing with WebSockets, CRDTs, and Rust

This article analyzes Feakin's approach to online collaborative diagram editing, covering communication protocols, data formats, the trade‑offs between centralized OT and decentralized CRDT algorithms, and the Rust‑based server and client implementation using Actix, WebSocket, and WebAssembly.

phodal
phodal
phodal
How Feakin Builds Real‑Time Collaborative Editing with WebSockets, CRDTs, and Rust

Online: Communication Protocol

Real‑time collaboration requires a persistent long‑lived connection. Feakin evaluates several protocols such as MQTT, CoAP, and LWM2M, but for browser‑based clients the simplest solution is WebSocket, which offers low learning curve and reliable bidirectional messaging.

Protocol: WebSocket vs HTTP

While WebSocket is the default, Feakin also experimented with the community‑driven Braid Protocol, which uses a custom HTTP status code (209 Subscriptions) and HTTP long‑polling for updates, and HTTP PATCH for sending changes. This approach enables peer‑to‑peer synchronization over HTTP while preserving automatic conflict resolution via OT/CRDT.

Data format: JSON vs Binary

During collaboration large amounts of data are exchanged. Current research favors binary formats for performance, but Feakin initially uses JSON for easier debugging and later plans to switch to a binary document representation.

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "type", content = "value")]
pub enum ActionType {
    CreateRoom(CreateRoom),
    JoinRoom(JoinRoom),
    LeaveRoom(LeaveRoom),
    // patches
    UpdateByVersion(UpdateByVersion),
    OpsByPatches(OpsByPatches),
}

The server side is built with Actix and WebSocket, handling patches and broadcasting them to all participants.

Collaboration Algorithm: Centralized vs Decentralized

Collaboration is fundamentally a consistency problem in distributed systems. Feakin discusses conflict scenarios (e.g., simultaneous edits) and outlines common resolution strategies such as locking, last‑write‑wins, and more sophisticated algorithms.

OT vs CRDT

Operational Transform (OT) is a centralized approach where a server receives client operations, transforms them, and pushes the transformed operations back to other clients. It works well for text but requires fine‑grained atomic operations.

Conflict‑Free Replicated Data Types (CRDT) are decentralized; clients can work offline and later merge changes without a central server. CRDTs support arbitrary JSON structures and are used in systems like Redis, Riak, and collaborative editors.

Feakin notes that OT is mature but can become complex in extreme cases, while CRDTs demand careful data‑model design. For rich‑text editing, CRDTs provide better extensibility.

Technology Choices: Rust and Diamond‑type

Feakin chooses Rust for its cross‑platform capabilities and compiles the CRDT logic to WebAssembly, allowing the same API to run on both server and browser.

Automerge‑RS: a full‑stack Rust solution for server, WASM, and JavaScript.

Y‑CRDT: a Rust‑based implementation leveraging the mature Y.js ecosystem.

Diamond‑type: a high‑performance Rust CRDT authored by a former Google Wave developer.

Feakin ultimately adopts Diamond‑type despite its incomplete API, valuing its performance characteristics.

Server Implementation (Actix + Diamond‑type + CRDT)

let before_version = live.lock().unwrap().version();
let after_version = self.ops_by_patches(agent_name, patches).await;
let patch = coding.patch_since(&before_version);

The server receives patches, merges them, and broadcasts the resulting state.

Client Implementation

Generating Patches

let localVersion = doc.getLocalVersion();
// sort changes by offset and apply them
changes.sort((c1, c2) => c2.rangeOffset - c1.rangeOffset).forEach(change => {
    doc.ins(change.rangeOffset, change.text);
    doc.del(change.rangeOffset, change.rangeLength);
});
let patch = doc.getPatchSince(localVersion);

Changes are turned into insert and delete operations, then packaged as a patch.

Applying Patches

let merge_version = doc.mergeBytes(bytes);
doc.mergeVersions(doc.getLocalVersion(), merge_version);
let xfSinces = doc.xfSince(patchInfo.before);
xfSinces.forEach(op => {
    // apply each operation to the editor (e.g., Monaco)
});

Applying patches to the Monaco editor requires inserting or deleting characters at specific positions and temporarily locking the editor to avoid race conditions.

Summary of Key Elements

Online: Choose appropriate communication protocol (WebSocket) and data format (JSON → binary).

Collaboration: Decide between centralized OT and decentralized CRDT based on consistency needs.

Editing: Implement multi‑device synchronization and editor integration using CRDT patches.

Future work includes optimizing JSON serialization, improving cursor visibility, handling edge‑case errors, and extending Feakin to support code‑centric DDD collaboration and architecture diagram generation.

distributed systemsRustWebSocketCRDTOTreal-time collaborationActix
phodal
Written by

phodal

A prolific open-source contributor who constantly starts new projects. Passionate about sharing software development insights to help developers improve their KPIs. Currently active in IDEs, graphics engines, and compiler technologies.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.