iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🏗️

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.

https://devtoolkits.app/ja/tools/infra-diagram-builder

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.
https://devtoolkits.app/

Discussion