<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nishant Bhatte</title>
    <description>The latest articles on DEV Community by Nishant Bhatte (@nishant_bhatte).</description>
    <link>https://dev.to/nishant_bhatte</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3978158%2Fc4a75bfd-5ebe-4bee-9d5d-21a8754f74b4.jpg</url>
      <title>DEV Community: Nishant Bhatte</title>
      <link>https://dev.to/nishant_bhatte</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nishant_bhatte"/>
    <language>en</language>
    <item>
      <title>Delete the backend: shipping v0.5 of a CRDT relay where the server can't read your data</title>
      <dc:creator>Nishant Bhatte</dc:creator>
      <pubDate>Sun, 05 Jul 2026 17:57:17 +0000</pubDate>
      <link>https://dev.to/nishant_bhatte/delete-the-backend-shipping-v05-of-a-crdt-relay-where-the-server-cant-read-your-data-4ghl</link>
      <guid>https://dev.to/nishant_bhatte/delete-the-backend-shipping-v05-of-a-crdt-relay-where-the-server-cant-read-your-data-4ghl</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Aether-Core deletes that entire stack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FIronFighter23%2Faether-core%2Fmain%2Fdocs%2Fmedia%2Fshopping-list-demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FIronFighter23%2Faether-core%2Fmain%2Fdocs%2Fmedia%2Fshopping-list-demo.gif" alt="Shopping list demo" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The pitch in one code snippet
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ShoppingList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setItems&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="c1"&gt;// ... plus API endpoint, database schema, auth middleware, ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ShoppingList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setItems&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAether&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
  &lt;span class="c1"&gt;// that's the whole change. multiplayer, offline, persisted.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's new in v0.5
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;End-to-end value encryption.&lt;/strong&gt; 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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AetherCrypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;roomFromPassphrase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;passphrase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;salt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aether&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Aether&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws://relay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;{"__ae_enc": 1, "iv": "...", "ct": "..."}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IndexedDB per-key persistence (now default).&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merkle-trie anti-entropy.&lt;/strong&gt; 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.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;One-command scaffold:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @nishantbhatte/aether-core init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Emits &lt;code&gt;relay.py&lt;/code&gt;, &lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;Procfile&lt;/code&gt;. Two commands from &lt;code&gt;npx init&lt;/code&gt; to a working relay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest positioning
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;aether-zta                  &lt;span class="c"&gt;# Python relay&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @nishantbhatte/aether-core  &lt;span class="c"&gt;# Browser client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or scaffold a working project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @nishantbhatte/aether-core init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/IronFighter23/aether-core" rel="noopener noreferrer"&gt;https://github.com/IronFighter23/aether-core&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;73 tests green on Linux/macOS/Windows × Python 3.11/3.12&lt;/li&gt;
&lt;li&gt;MIT licensed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback welcome. Especially interested in "here's where I'd break it" from anyone who's built collaborative apps before.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Deleted My Backend. My Frontend Variables Are Now the Database</title>
      <dc:creator>Nishant Bhatte</dc:creator>
      <pubDate>Fri, 26 Jun 2026 16:51:15 +0000</pubDate>
      <link>https://dev.to/nishant_bhatte/i-deleted-my-backend-my-frontend-variables-are-now-the-database-4dg2</link>
      <guid>https://dev.to/nishant_bhatte/i-deleted-my-backend-my-frontend-variables-are-now-the-database-4dg2</guid>
      <description>&lt;p&gt;Stamps form a &lt;strong&gt;strictly total order&lt;/strong&gt;. Given any two stamps, exactly one is greater. No ties. Ever. When two tabs write the same key concurrently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The higher-stamp write wins&lt;/li&gt;
&lt;li&gt;Every replica picks the &lt;strong&gt;same&lt;/strong&gt; winner deterministically&lt;/li&gt;
&lt;li&gt;No conflict-resolution UI needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deletes are stored as &lt;strong&gt;tombstones&lt;/strong&gt;, not removals. A stale write that arrives late saying "key X = 5" cannot resurrect a deleted key — the tombstone with a higher stamp always wins.&lt;/p&gt;

&lt;p&gt;This isn't theoretical. The math is provably commutative, associative, and idempotent. I hammered it with 1,000 randomised concurrent operations across 3 simulated nodes and asserted all replicas converge byte-for-byte. The test passes 100% of the time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Offline-first comes for free
&lt;/h2&gt;

&lt;p&gt;Here's the part that genuinely felt like magic the first time it worked.&lt;/p&gt;

&lt;p&gt;The JavaScript client maintains a &lt;code&gt;localStorage&lt;/code&gt; cache. On every page load, it reads from cache &lt;strong&gt;synchronously, before the WebSocket even tries to connect&lt;/strong&gt;. If the cache is hot, the UI renders &lt;strong&gt;instantly&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Stop the relay. Refresh the tab. The app still works. The browser shows your state from cache. When the relay comes back online, the WebSocket reconnects and the gateway snapshot patches any divergence.&lt;/p&gt;

&lt;p&gt;Measured: &lt;strong&gt;&lt;code&gt;ready()&lt;/code&gt; resolves in 1 ms&lt;/strong&gt; when the cache is hot. That's the difference between "instant render" and "wait for a network round-trip that might never complete."&lt;/p&gt;




&lt;h2&gt;
  
  
  Real numbers (not vibes)
&lt;/h2&gt;

&lt;p&gt;I shipped a benchmark harness with the repo. Run it yourself with &lt;code&gt;uv run aether-benchmark&lt;/code&gt;. Numbers from a Linux VM, Python 3.12, fsync ON:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gateway round-trip (loopback, p50)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.21 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway round-trip (loopback, p99)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.68 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ledger replay throughput&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~86,000 ops/sec&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snapshot-boot speedup vs cold replay&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.6 – 1.8×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mesh convergence (5-node ring, 100 writes)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;63 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ledger write throughput (with fsync)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~1,400 ops/sec&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The relay isn't your bottleneck. The network is.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's actually in the box
&lt;/h2&gt;

&lt;p&gt;Three working "killer" demo apps, all open in two tabs and sync in real time:&lt;/p&gt;

&lt;p&gt;🗂️ &lt;strong&gt;Kanban board&lt;/strong&gt; — columns, drag-drop cards between columns, priority cycling, delete-cascade&lt;br&gt;
📝 &lt;strong&gt;Collaborative Markdown editor&lt;/strong&gt; — paragraph-keyed CRDT, live HTML preview pane&lt;br&gt;
🖧 &lt;strong&gt;Network topology diagrammer&lt;/strong&gt; — drag-drop devices, typed links (1G/10G/Wi-Fi), labels&lt;/p&gt;

&lt;p&gt;Plus the actual engine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CRDT layer&lt;/strong&gt;: Hybrid Logical Clock + LWW Map (&lt;code&gt;crdt.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mesh layer&lt;/strong&gt;: WebSocket gossip with epidemic relay between Python nodes (&lt;code&gt;mesh.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt;: append-only JSON-lines ledger with crash recovery (&lt;code&gt;storage.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gateway&lt;/strong&gt;: browser-facing WebSocket endpoint, rate-limited and payload-capped (&lt;code&gt;gateway.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: token bucket, connection caps, payload validators, slow-loris guard (&lt;code&gt;_security.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser SDK&lt;/strong&gt;: 700 lines of vanilla JS, no build step, no framework (&lt;code&gt;aether.js&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript declarations&lt;/strong&gt;: full &lt;code&gt;.d.ts&lt;/code&gt; covering every method (&lt;code&gt;aether.d.ts&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the boring-but-important stuff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;26 automated tests&lt;/strong&gt; (17 Python pytest + 9 JS scenarios)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol conformance tests&lt;/strong&gt; that parse the README and assert the running server matches it (so the docs cannot silently drift from the code)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Threat model documentation&lt;/strong&gt; with every mitigation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8-file documentation suite&lt;/strong&gt;: getting started, concepts, two API references, recipes, deployment guide, troubleshooting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing is &lt;strong&gt;MIT-licensed&lt;/strong&gt; and depends on exactly one Python library (&lt;code&gt;websockets&lt;/code&gt;) and zero JavaScript libraries.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this is &lt;em&gt;not&lt;/em&gt; for
&lt;/h2&gt;

&lt;p&gt;I owe you the limits, because none of this is free magic.&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;High-cardinality data&lt;/strong&gt; (millions of keys). Every new browser tab gets the full snapshot.&lt;br&gt;
❌ &lt;strong&gt;Fine-grained authorization.&lt;/strong&gt; Every connected client sees every key.&lt;br&gt;
❌ &lt;strong&gt;Character-level collaborative editing of the same line.&lt;/strong&gt; LWW means one writer wins per field. (My Markdown editor sidesteps this by keying each &lt;em&gt;paragraph&lt;/em&gt; separately.)&lt;br&gt;
❌ &lt;strong&gt;Strong consistency.&lt;/strong&gt; CRDTs are eventually consistent by definition.&lt;br&gt;
❌ &lt;strong&gt;Public-facing apps with thousands of concurrent users per relay.&lt;/strong&gt; You can shard by document/room, but you do it manually.&lt;/p&gt;

&lt;p&gt;Where it shines: &lt;strong&gt;internal tools, prototypes, niche collaborative editors, multi-tab desktop-app-like web UIs, anything where 5–50 people share state.&lt;/strong&gt; The whole class of apps where you'd normally write a 200-line Express server, a 5-table Postgres schema, and 6 REST endpoints — and where 90% of that work exists only to ferry the same data between representations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/IronFighter23/aether-core
&lt;span class="nb"&gt;cd &lt;/span&gt;aether-core
uv &lt;span class="nb"&gt;sync
&lt;/span&gt;uv run aether-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three URLs in two tabs each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;http://localhost:8080/&lt;/a&gt; — topology diagrammer&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://localhost:8080/demos/kanban.html" rel="noopener noreferrer"&gt;http://localhost:8080/demos/kanban.html&lt;/a&gt; — Kanban&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://localhost:8080/demos/markdown.html" rel="noopener noreferrer"&gt;http://localhost:8080/demos/markdown.html&lt;/a&gt; — Markdown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kill the server. Refresh. Notice the state is still there.&lt;/p&gt;




&lt;h2&gt;
  
  
  Star, fork, tell me what to break
&lt;/h2&gt;

&lt;p&gt;The repo: &lt;strong&gt;&lt;a href="https://github.com/IronFighter23/aether-core" rel="noopener noreferrer"&gt;github.com/IronFighter23/aether-core&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'd genuinely love to know:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What's the smallest real app you'd build on this?&lt;/strong&gt; Drop it in the comments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where does the model break for you?&lt;/strong&gt; I want to find the edges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Should I add a Redis driver for the federation layer?&lt;/strong&gt; That'd let you run this on AWS/GCP without exposing the mesh port publicly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you build something on top of Aether-Core, tag me — I'll happily add it to the README's "built with" list.&lt;/p&gt;

&lt;p&gt;If this post helped you think differently about backends for a minute, leave a ❤️ or a 🦄. If it didn't, tell me why in the comments. I'm wrong about plenty of things and would rather find out now than later.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://github.com/IronFighter23" rel="noopener noreferrer"&gt;Nishant Bhatte&lt;/a&gt;. MIT licensed. 4,000 lines of Python + JavaScript + Markdown.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built a zero-dependency env variable validator for Node.js — env-guard</title>
      <dc:creator>Nishant Bhatte</dc:creator>
      <pubDate>Wed, 10 Jun 2026 17:57:03 +0000</pubDate>
      <link>https://dev.to/nishant_bhatte/i-built-a-zero-dependency-env-variable-validator-for-nodejs-env-guard-3l78</link>
      <guid>https://dev.to/nishant_bhatte/i-built-a-zero-dependency-env-variable-validator-for-nodejs-env-guard-3l78</guid>
      <description>&lt;p&gt;Every Node.js project reads from process.env. But process.env gives you raw strings — no types, no validation, no defaults.&lt;/p&gt;

&lt;p&gt;This leads to silent bugs like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PORT is undefined and your app crashes&lt;/li&gt;
&lt;li&gt;ENABLE_CACHE === "false" is truthy in JS&lt;/li&gt;
&lt;li&gt;Missing DATABASE_URL discovered at runtime, not startup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I got tired of writing the same boilerplate fix in every project, so I built env-guard.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Zero dependencies — pure Node.js&lt;/li&gt;
&lt;li&gt;Type coercion — strings become numbers, booleans, parsed JSON&lt;/li&gt;
&lt;li&gt;Collects ALL errors at once — see every problem in one error&lt;/li&gt;
&lt;li&gt;Beautiful error output — with hints and examples in the terminal&lt;/li&gt;
&lt;li&gt;Built-in .env loader — no dotenv needed&lt;/li&gt;
&lt;li&gt;Auto-generates .env.example from your schema&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Install it:&lt;br&gt;
npm install @ironfighter23/env-guard&lt;/p&gt;

&lt;p&gt;Define your schema once:&lt;/p&gt;

&lt;p&gt;const { guard, schema } = require('@ironfighter23/env-guard');&lt;/p&gt;

&lt;p&gt;const config = guard({&lt;br&gt;
  PORT:         schema.port().default(3000),&lt;br&gt;
  DATABASE_URL: schema.url().sensitive(),&lt;br&gt;
  NODE_ENV:     schema.enum(['development', 'production']),&lt;br&gt;
  SECRET_KEY:   schema.string().min(32),&lt;br&gt;
  ENABLE_CACHE: schema.boolean().default(false),&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;If anything is wrong you see ALL errors at once with hints — not one at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/IronFighter23/env-guard" rel="noopener noreferrer"&gt;https://github.com/IronFighter23/env-guard&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/@ironfighter23/env-guard" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@ironfighter23/env-guard&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would love your feedback!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>node</category>
    </item>
  </channel>
</rss>
