I Built a Group Chat That Draws Itself Into a Graph - Here's How
Ever been in a group chat where great ideas just... disappear? You're brainstorming with your team, someone drops a brilliant insight, and 30 messages later it's buried forever.
I got tired of scrolling through walls of text trying to find "that thing someone said earlier," so I built Qonvo — a group chat that visualizes conversations as an interactive graph in real-time.
The Problem
Traditional chat is linear. But conversations aren't.
We jump between topics, reference earlier points, and connect ideas across threads. Forcing all of this into a single scrolling column loses context and makes it hard to see how ideas relate.
The Solution
What if every message was a node, every reply was a connection, and you could see the entire conversation at once?
That's Qonvo. As you chat, a force-directed graph builds in real-time:
- Messages → Nodes (color-coded by sender)
- Replies → Solid edges (direct connections)
- #Tags → Dashed edges (link related ideas across threads)
Click any node to focus it and see its connections. The graph updates live as people chat.
The Tech Stack
Built this as a solo project in about 2 weeks:
- Frontend: Vanilla JS + D3.js (no React, no build step)
- Backend: Node.js + Express + Socket.io
- Persistence: Redis (Upstash) with 3-hour TTL
- Hosting: Railway
Why Vanilla JS?
Honestly? Speed. I wanted to ship fast without wrestling with bundlers. The entire frontend is a single 1,800-line HTML file. Is it perfect? No. Does it work? Yes.
D3's force simulation handles the graph physics. Socket.io keeps everyone in sync. That's really it.
The Core Graph Logic
// Every message becomes a node
const node = {
id: nanoid(12),
userId: sender.id,
name: sender.name,
text: message,
tags: extractTags(message), // #hashtags
ts: Date.now()
};
// Replies create edges
if (replyTo) {
edges.push({
source: replyTo,
target: node.id,
type: "reply"
});
}
// Tags connect related messages
for (const tag of node.tags) {
const lastWithTag = findLastMessageWithTag(tag);
if (lastWithTag) {
edges.push({
source: lastWithTag.id,
target: node.id,
type: "tag",
tag
});
}
}
What I Learned
1. Ephemeral is a feature, not a bug
Rooms auto-delete after 3 hours. No accounts, no sign-ups, no data hoarding. Users actually love this — it makes the tool feel safe for quick, throwaway conversations.
2. The graph needs to be optional
On mobile, showing chat + graph doesn't work. So there's a toggle to open the graph as an overlay. Desktop shows both side-by-side.
3. Force simulations are tricky
Getting nodes to not overlap while keeping connected nodes close took a lot of tweaking:
simulation
.force("charge", d3.forceManyBody().strength(-280))
.force("collision", d3.forceCollide().radius(45))
.force("link", d3.forceLink(edges).distance(90));
Try It
Live: qonvo.xyz
There's a demo room with sample data, or create your own room and share the link.
No sign-up. No install. Just click and chat.
What's Next
- Paid room extensions — Keep rooms alive for 7/30 days instead of 3 hours
- Export to Markdown — Turn conversations into documentation
- PWA support — Installable on mobile
Feedback Welcome
This is my first real "ship it and see" project. Would love to hear:
- Is the visualization actually useful, or just a gimmick?
- What would make you use this over a regular group chat?
- Any UX issues I should fix?
Drop a comment or try the demo and let me know what breaks.
Building in public as a solo founder. Follow along: @digi_wares

Top comments (0)