Dev.to WebDev 🛠 Dev 👁 1 📖 2 min read

Delete the backend: shipping v0.5 of a CRDT relay where the server can't read your data

Yjs, Automerge, and Loro are excellent CRDT libraries — but they leave the server problem to you. You still write API endpoints. You still design a persistence layer. You still bolt on auth. Aether-Core deletes that ent

Delete the backend: shipping v0.5 of a CRDT relay where the server can't read your data

Yjs, Automerge, and Loro are excellent CRDT libraries — but they leave the server problem to you. You still write API endpoints. You still design a persistence layer. You still bolt on auth.

Aether-Core deletes that entire stack.

Shopping list demo

The pitch in one code snippet

Before:

function ShoppingList() {
  const [items, setItems] = useState([]);
  // ... plus API endpoint, database schema, auth middleware, ...
}

After:

function ShoppingList() {
  const [items, setItems] = useAether('items', []);
  // that's the whole change. multiplayer, offline, persisted.
}

The Python relay treats every value as opaque bytes. It doesn't implement business logic. It doesn't have endpoints. It cannot read your data.

What's new in v0.5

End-to-end value encryption. Optional client-side AES-256-GCM wraps every value before it leaves the browser. The relay sees ciphertext only. Even someone who runs the relay for you can't read your data:

const room = await AetherCrypto.roomFromPassphrase("passphrase", "salt");
const aether = new Aether("ws://relay", { transform: room.transform });

Verified end-to-end with a wire observer: two clients round-trip plaintext through the room key, while a third socket sniffing the same relay sees only {"__ae_enc": 1, "iv": "...", "ct": "..."}.

IndexedDB per-key persistence (now default). The previous localStorage default hit two hard problems: the 5-10 MB quota ceiling, and JSON-serialize-the-whole-blob on every write. Both fixed. localStorage remains as a backward-compatible fallback for private mode.

Merkle-trie anti-entropy. When two peers reconnect after a partition, epidemic gossip alone can't recover the ops that flowed while they were split. Aether-Core now ships a Merkle-trie digest exchange. Bandwidth is O(k log N) in the number of divergent keys, not O(N) total. A 1000-key mesh with 3 divergent keys reconciles in ~16 probe messages, not 1000.

LAN peer discovery via mDNS. Two relays on the same wifi find each other with zero config. Opt-in (MeshNode(discovery=True)) because some networks treat mDNS traffic as suspicious.

One-command scaffold:

npx @nishantbhatte/aether-core init

Emits relay.py, index.html, Dockerfile, Procfile. Two commands from npx init to a working relay.

Honest positioning

I'm not competing with Yjs on CRDT math. Yjs is more mature, has a bigger ecosystem, and its sync engine is excellent. The wedge is server-side: I ship auth, rate limiting, payload caps, and optional E2E encryption on by default. Yjs leaves that to you.

Yjs / Automerge / Loro Aether-Core
CRDT sync ✅ ✅
Backend code required You write it None
Auth built-in ❌ bring your own ✅ HMAC by default
Rate limiting built-in ❌ ✅ token bucket
Payload caps built-in ❌ ✅ configurable
E2E crypto in recommended path Ecosystem addon Ships in web/aether-crypto.js
Anti-entropy after partition State-vector sync (O(delta)) Merkle-trie (O(k log N))
LAN auto-discovery ❌ ✅ mDNS opt-in

Try it

pip install aether-zta                  # Python relay
npm install @nishantbhatte/aether-core  # Browser client

Or scaffold a working project:

npx @nishantbhatte/aether-core init

Feedback welcome. Especially interested in "here's where I'd break it" from anyone who's built collaborative apps before.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.