<?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: Victor Agudo Gonzalez</title>
    <description>The latest articles on DEV Community by Victor Agudo Gonzalez (@victor_agudogonzalez_51b).</description>
    <link>https://dev.to/victor_agudogonzalez_51b</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%2F4009728%2F9463d706-ac1a-4546-9392-f38e0cdb988b.jpg</url>
      <title>DEV Community: Victor Agudo Gonzalez</title>
      <link>https://dev.to/victor_agudogonzalez_51b</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/victor_agudogonzalez_51b"/>
    <language>en</language>
    <item>
      <title>How I built a real-time collaborative diagram canvas (React + Go)</title>
      <dc:creator>Victor Agudo Gonzalez</dc:creator>
      <pubDate>Tue, 30 Jun 2026 14:19:01 +0000</pubDate>
      <link>https://dev.to/victor_agudogonzalez_51b/how-i-built-a-real-time-collaborative-diagram-canvas-react-go-4g2o</link>
      <guid>https://dev.to/victor_agudogonzalez_51b/how-i-built-a-real-time-collaborative-diagram-canvas-react-go-4g2o</guid>
      <description>&lt;p&gt;I wanted a fast way to sketch architecture diagrams and share them with other people live, without installing anything and without the heavy, corporate feel of most diagram tools. So I built Diagraw: a free infinite canvas with a hand-drawn look and real-time collaboration.&lt;/p&gt;

&lt;p&gt;It is free and works without an account. Here are the parts that were the most interesting to build.&lt;/p&gt;

&lt;p&gt;Edges are derived, never stored&lt;/p&gt;

&lt;p&gt;The first decision that paid off: node positions are the only source of truth. An edge just references the two nodes it connects (or two free points). The actual arrow geometry, clipped to each node's border, is recomputed on every render.&lt;/p&gt;

&lt;p&gt;That sounds wasteful, but it means moving a node keeps its arrows attached for free. There is no "update the arrows" code path, because arrows have no stored geometry to update. Geometry lives in world coordinates and is converted to screen coordinates only at render time (screen = world * zoom + translate), so panning and zooming never touch the data.&lt;/p&gt;

&lt;p&gt;The hand-drawn look, without rough.js&lt;/p&gt;

&lt;p&gt;The sketchy style is a small self-contained generator: a seeded PRNG&lt;br&gt;
(mulberry32) keyed by a hash of each node's id. The same node always perturbs the same way, so shapes do not "wobble" between renders, and they are only recomputed when a cache key changes (id + type + rounded size + color + roughness). No external drawing library.&lt;/p&gt;

&lt;p&gt;Real-time collaboration: a small Go hub&lt;/p&gt;

&lt;p&gt;The backend is a Go service with an in-memory WebSocket hub. Clients diff their local store into deltas (upsert / remove by id) and send only those; the hub relays them to the other participants and persists to MongoDB with a debounce, so a burst of edits is one write, not hundreds.&lt;/p&gt;

&lt;p&gt;Live cursors and presence are ephemeral messages that are relayed but never persisted. There is also a "presenter" mode: the owner broadcasts their viewport (throttled) so everyone else can follow their camera, which is great for walking a team through a diagram.&lt;/p&gt;

&lt;p&gt;The honest limitation&lt;/p&gt;

&lt;p&gt;Because the hub is in-process, the API runs at one replica per environment. It does not scale horizontally yet. The blocker is the in-memory hub, not the data;&lt;br&gt;
a shared pub/sub (Redis, NATS) would fix it. I have not needed it yet, and I would rather ship and learn than pre-scale for traffic I do not have.&lt;/p&gt;

&lt;p&gt;Importing Mermaid and docker-compose&lt;/p&gt;

&lt;p&gt;You can paste a Mermaid flowchart or a docker-compose file and Diagraw draws it for you. Two pieces make this work:&lt;/p&gt;

&lt;p&gt;A Sugiyama-style layered layout (longest-path ranking with back-edge handling) to place the nodes.&lt;br&gt;
Edge routing that bends a connector around groups instead of crossing them, turning it into a smooth curve when a straight line would cut through a layer.&lt;/p&gt;

&lt;p&gt;Getting the import to match what people drew in Mermaid was more fiddly than the&lt;br&gt;
layout itself (edge labels that became phantom nodes, quoted subgraphs, shape&lt;br&gt;
mapping). Inverse parsers are never as clean as the generators.&lt;/p&gt;

&lt;p&gt;Stack and infra&lt;/p&gt;

&lt;p&gt;Front end: React + TypeScript + Vite. Two zustand stores: one persisted document, one for transient gesture state that must not enter undo history.&lt;br&gt;
Backend: Go, hexagonal-ish (domain / use cases / adapters), Mongo or in-memory.&lt;br&gt;
Infra: a single small VPS, Docker Swarm behind Traefik, Cloudflare in front, daily off-site encrypted Mongo backups, CI-gated deploys.&lt;/p&gt;

&lt;p&gt;Try it / tell me where it breaks&lt;/p&gt;

&lt;p&gt;It is still early. If you draw something, I would love to know what felt slow or&lt;br&gt;
confusing, and whether the Mermaid import did what you expected.&lt;/p&gt;

&lt;p&gt;Link: &lt;a href="https://diagraw.com" rel="noopener noreferrer"&gt;Diagraw.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>react</category>
      <category>go</category>
      <category>webdev</category>
      <category>sideprojects</category>
    </item>
  </channel>
</rss>
