DEV Community

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

Posted on

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, ...
}
Enter fullscreen mode Exit fullscreen mode

After:

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

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 });
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Or scaffold a working project:

npx @nishantbhatte/aether-core init
Enter fullscreen mode Exit fullscreen mode

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

Top comments (5)

Collapse
 
alexshev profile image
Alex Shev

This is the kind of "delete the backend" architecture that only works if the trust model is explicit. If the relay cannot read the data, the client-side conflict and recovery story becomes the product.

Collapse
 
nishant_bhatte profile image
Nishant Bhatte

Yeah, exactly — that's actually by design, not just marketing. The merge logic and anti-entropy reconciliation never look at the value at all, only the HLC stamp, so the relay was always metadata-only even before encryption entered the picture. E2E just makes explicit what was already true architecturally. The honest gap right now: keys are plaintext unless you opt into hashing, and there's no write authentication yet — anyone with the room secret can write, so it's shared-trust rather than zero-trust today. That's the next thing on the list.

Collapse
 
alexshev profile image
Alex Shev

That design constraint is strong: if the relay only reasons over stamps and reconciliation metadata, encryption becomes a confirmation of the architecture rather than a privacy sticker added later. Write authentication feels like the next honest boundary to make explicit.

Thread Thread
 
nishant_bhatte profile image
Nishant Bhatte

Agreed, that's the honest next line to draw. Right now it's shared-trust: anyone with the room secret can write, so "the relay can't read it" doesn't imply "the relay can vouch for who wrote it." Making that explicit is easy — the harder part is actually building it (per-writer keys, revocation, signature verification wired into the merge path), so for now it's a documented boundary rather than a shipped feature. It's next on the roadmap, not next in this release.

Thread Thread
 
alexshev profile image
Alex Shev

That is the honest tradeoff. "Server cannot read the payload" is a strong privacy property, but authorship and abuse control need their own layer. I would rather see that boundary stated plainly than hidden behind a general "secure" claim.