iTranslated by AI
I Built a Browser-Based Infrastructure Diagram Tool (Astro / TypeScript / Mermaid.js)
Introduction
Have you ever had a moment where you just wanted to "quickly sketch" an infrastructure diagram?
It's not quite worth launching draw.io or Lucidchart, but drawing it by hand is a hassle... For those moments, I built an infrastructure diagram builder that runs entirely in the browser.
Simply by adding nodes and connections, it automatically generates a Mermaid.js format diagram. You can download it as a PNG image or copy the Mermaid code to paste into a GitHub README.
In this article, I will explain the design of the InfraDiagramBuilder class—the core of the implementation—and the logic for converting to Mermaid syntax.
Tool Features
- Browser-based: No server communication; your input data never leaves your device.
- Mermaid.js Adoption: The output code can be pasted directly into GitHub or Notion for rendering.
- Multiple Shapes & Connection Styles: Choose shapes suited for your purpose, such as servers, databases, or gateways.
Data Structure Design
The state of the tool is represented by three interfaces.
export interface InfraNode {
id: string;
label: string;
type: 'default' | 'database' | 'cloud' | 'user' | 'server' | 'storage' | 'network' | 'gateway';
shape?: 'round' | 'stadium' | 'subroutine' | 'cylinder' | 'circle' | 'diamond';
}
export interface InfraConnection {
id: string;
from: string;
to: string;
label?: string;
style?: 'default' | 'dotted' | 'thick' | 'arrow' | 'no-arrow';
}
export interface InfraDiagramState {
nodes: InfraNode[];
connections: InfraConnection[];
direction: 'TB' | 'BT' | 'LR' | 'RL';
}
InfraNode separates the visual appearance (shape) from the meaning (type). This design allows for future extensions, such as displaying icons based on the type.
Core of Mermaid Conversion: InfraDiagramBuilder.toMermaid()
The main logic of this class is to generate the Mermaid graph syntax from the state object.
export class InfraDiagramBuilder {
static toMermaid(state: InfraDiagramState): string {
if (!state.nodes || state.nodes.length === 0) {
return '';
}
let mermaid = `graph ${state.direction || 'TB'}\n`;
// Rendering nodes
state.nodes.forEach((node) => {
const label = node.label || node.id;
const openBracket = this.getOpenBracket(node.shape);
const closeBracket = this.getCloseBracket(node.shape);
mermaid += ` ${node.id}${openBracket}"${label}"${closeBracket}\n`;
});
// Rendering connections
state.connections.forEach((conn) => {
if (!conn.from || !conn.to) return;
const line = this.getConnectionLine(conn.style);
const label = conn.label ? `|${conn.label}| ` : '';
mermaid += ` ${conn.from} ${line} ${label}${conn.to}\n`;
});
return mermaid;
}
}
Mapping Node Shapes
In Mermaid's graph syntax, node shapes are represented by combinations of brackets.
| Shape | Mermaid Syntax | Use Case |
|---|---|---|
| Default (Rectangle) | [label] |
Servers, Components |
| Rounded Edges | (label) |
Services, Apps |
| Stadium | ([label]) |
External Services |
| Subroutine | [[label]] |
Process Blocks |
| Circle/Cylinder | ((label)) |
Databases, Caches |
| Diamond | {label} |
Decision points, Gateways |
private static getOpenBracket(shape: InfraNode['shape']): string {
switch (shape) {
case 'round': return '(';
case 'stadium': return '([';
case 'subroutine': return '[[';
case 'cylinder': return '((';
case 'circle': return '((';
case 'diamond': return '{';
default: return '[';
}
}
Mapping Connection Styles
Connecting lines also support multiple styles.
private static getConnectionLine(style: InfraConnection['style']): string {
switch (style) {
case 'dotted': return '-.->'; // Dotted arrow (asynchronous/optional)
case 'thick': return '==>'; // Thick arrow (main flow)
case 'no-arrow': return '---'; // No arrow (physical connection)
default: return '-->'; // Normal arrow
}
}
Actual output looks like this:
Integration with UI (Astro Component)
The UI is implemented using Astro + Vanilla JS. The reactive update function, which runs every time a node or connection is modified, calls InfraDiagramBuilder.toMermaid().
function updateDiagram() {
const code = InfraDiagramBuilder.toMermaid(currentState);
// Re-rendering with Mermaid.js
mermaid.render('mermaid-diagram', code).then(({ svg }) => {
previewEl.innerHTML = svg;
});
// Updating the code view as well
codeEl.textContent = code;
}
By having a simple design where updateDiagram() is called whenever the state changes, this is achieved without the need for state management frameworks like React.
Commitment to Being Browser-Only
The design policy for DevToolkits is "Safe & Simple".
Infrastructure diagrams often contain sensitive information, including internal system designs. Uploading such data to a server can be concerning—by making it work entirely within the browser, I aimed to create a tool that can be used with peace of mind even from a company PC.
Conclusion
- Manage UI state simply with
InfraDiagramState. - Convert to Mermaid syntax with
InfraDiagramBuilder.toMermaid(). - Represent shapes and connection styles through combinations of brackets and connecting lines.
Since Mermaid syntax itself is simple, the state-to-text conversion was very straightforward to write. It is the perfect tool for the need to "visually manipulate nodes without going as far as importing a heavy library."
Please give it a try!
I also publish other useful tools.
Discussion