Back to KB
Difficulty
Intermediate
Read Time
8 min

Mermaid.js - Sketching diagrams using code

By Codcompass Team··8 min read

Architecture as Text: Building Version-Controlled System Diagrams with Mermaid.js

Current Situation Analysis

Visual architecture documentation is notoriously fragile in modern engineering workflows. Traditional diagramming tools export static assets (PNG, SVG, or proprietary formats) that live outside the codebase. When implementation details shift, diagrams rot. Teams spend disproportionate time reconciling outdated visuals with current code, leading to onboarding friction, misaligned cross-team communication, and delayed incident resolution.

This problem is frequently misunderstood because organizations treat diagrams as presentation deliverables rather than living engineering artifacts. The cognitive overhead of switching between an IDE and a GUI drawing tool breaks developer flow. More critically, static diagrams cannot be reviewed in pull requests, diffed across commits, or validated by automated pipelines.

Mermaid.js addresses this by embedding diagram syntax directly into markdown. The engine parses text blocks at render time, eliminating asset management overhead. Because the syntax is plain text, diagrams become first-class citizens in version control. When paired with modern LLMs, documentation latency drops from hours to minutes. The shift from drag-and-drop canvases to declarative text transforms diagrams from fragile exports into maintainable, reviewable, and programmatically generatable components of the codebase.

WOW Moment: Key Findings

The operational advantage of text-based diagramming becomes quantifiable when comparing traditional GUI workflows against Mermaid-as-code. The following comparison isolates the metrics that directly impact engineering velocity and documentation reliability.

ApproachVersion Control CompatibilityAI-Assisted GenerationRender LatencyMaintenance OverheadCollaboration Friction
Traditional GUI ToolsLow (binary/proprietary exports)Manual transcription requiredHigh (manual export/import)High (drifts from code)High (context switching)
Mermaid-as-CodeNative (diffable markdown)Direct LLM generationNear-zero (browser/editor native)Low (updated with PRs)Low (reviewed in-line)

This finding matters because it repositions documentation from a post-implementation chore to a parallel engineering activity. When diagrams are text, they can be linted, tested, regenerated, and reviewed alongside code changes. The ability to feed source code directly into an LLM and receive valid Mermaid syntax compresses the documentation feedback loop, ensuring architecture visuals evolve at the same velocity as the implementation.

Core Solution

Building production-ready diagrams with Mermaid.js requires a disciplined approach to syntax structure, layout strategy, and AI integration. The following implementation demonstrates a complete workflow for documenting an order fulfillment pipeline.

Step 1: Establish the Rendering Boundary

Mermaid diagrams live inside markdown code blocks. The renderer identifies the block by the mermaid language tag. Modern platforms (GitHub, GitLab, VS Code, Notion) natively parse this syntax. No external dependencies are required for basic rendering.

```mermaid
graph LR
    A[Order API] --> B[Validation Service]

### Step 2: Construct a Topology Flowchart

Flowcharts map system components and their relationships. Use explicit node IDs to prevent layout collisions, and group related services using subgraphs. Subgraphs improve readability and allow targeted styling.

```mermaid
graph TD
    subgraph Ingestion
        API[Order Gateway]
        Auth[Auth Middleware]
    end

    subgraph Processing
        Val[Validation Engine]
        Queue[Message Broker]
    end

    subgraph Storage
        DB[(Order Database)]
        Cache[Redis Cache]
    end

    API -->|HTTPS| Auth
    Auth -->|Token Verified| Val
    Val -->|Valid| Queue
    Val -->|Invalid| API
    Queue --> DB
    Queue --> Cache

Architecture Decision: We use graph TD (top-down) instead of graph LR (left-right) because vertical layouts scale better for multi-tier architectures. Explicit node IDs (API, Auth, Val) decouple visual labels from internal references, making refactoring safer. Subgraphs create logical boundaries that align with bounded contexts in domain-driven design.

Step 3: Map Execution Flow with Sequence Diagrams

Sequence diagrams capture temporal interactions. They are essential for documenting API contracts, async workflows, and error handling paths. Mermaid supports alt, opt, and loop blocks to represent conditional logic without cluttering the main flow.

sequenceDiagram
    participant Client
    participant Gateway as Order Gateway
    participant Validator as Validation Engine
    participant Broker as Message Queue
    participant Persistence as Order DB

    Client->>Gateway: POST /orders {payload}
    Gateway->>Validator: validate(payload)
    
    alt Schema Valid
        Validator-->>Gateway: 200 OK
        Gateway->>Broker: publish(order_event)
        Broker->>Persistence: insert(order_record)
        Persistence-->>Broker: ack
        Broker-->>Gateway: delivery_confirmed
        Gateway-->>Client: 201 Created {order_id}
    else Schema Invalid
        Validator-->>Gateway: 400 Bad Request
        Gateway-->>Client: 400 {errors}
    end

Architecture Decision: Sequence diagrams are reserved for runtime interactions, not static topology. We alias participants (participant Broker as Message Queue) to keep labels concise while preserving technical accuracy. The alt block explicitly do

cuments the failure path, which is frequently omitted in manual diagrams but critical for production readiness.

Step 4: Integrate AI-Assisted Generation

LLMs excel at translating structured source code into Mermaid syntax. The key is providing precise context and output constraints. Instead of asking for a generic diagram, specify the diagram type, participants, and expected flow boundaries.

Effective Prompt Structure:

Analyze the following TypeScript controller and service layer. Generate a Mermaid sequence diagram showing the request lifecycle from HTTP entry to database persistence. Include error handling branches. Use participant aliases for clarity. Output only the mermaid code block.

Source Context (Example):

// order.controller.ts
export class OrderController {
  constructor(private readonly orderService: OrderService) {}

  async createOrder(@Body() dto: CreateOrderDto) {
    const validated = await this.orderService.validate(dto);
    return this.orderService.persist(validated);
  }
}

// order.service.ts
export class OrderService {
  async validate(dto: CreateOrderDto) {
    if (!dto.items.length) throw new BadRequestException('Empty cart');
    return dto;
  }

  async persist(dto: CreateOrderDto) {
    const record = await this.repo.save(dto);
    return { id: record.id, status: 'pending' };
  }
}

The LLM maps the controller-service-repository chain to participants, infers the synchronous validation step, and structures the sequence diagram accordingly. This workflow reduces documentation overhead by 70-80% while maintaining technical accuracy.

Pitfall Guide

1. Node ID Collisions

Explanation: Mermaid uses node IDs as internal references. Reusing IDs across different diagrams in the same markdown file, or using reserved keywords (graph, subgraph, end), causes rendering failures or silent layout breaks. Fix: Prefix IDs with a diagram-specific namespace (e.g., ORD_API, SEQ_VAL). Maintain a naming convention document for your repository. Avoid reserved Mermaid keywords entirely.

2. Over-Nesting Subgraphs

Explanation: Deeply nested subgraphs confuse the layout engine. Mermaid's automatic positioning algorithm struggles with more than two levels of nesting, resulting in overlapping edges or collapsed nodes. Fix: Limit subgraphs to two levels. Extract complex subsystems into separate diagrams and link them via cross-references. Use dashed lines or explicit labels to indicate external dependencies instead of nesting.

3. Ignoring Renderer Limits

Explanation: Mermaid has built-in constraints to prevent browser memory exhaustion. Exceeding ~50 nodes or ~100 edges in a single diagram triggers truncation or silent failures in GitHub/VS Code renderers. Fix: Split large architectures into focused diagrams (topology, data flow, deployment). Use click directives to link related diagrams. Validate diagram size before committing.

4. Hardcoding Colors Instead of Using Themes

Explanation: Inline styling (style A fill:#f9f,stroke:#333) bloats syntax and breaks when platform themes change (e.g., GitHub dark mode). It also prevents consistent branding across documentation. Fix: Define themes in a centralized configuration. Use semantic class assignments (classDef primary fill:#e1f5fe,stroke:#0288d1) and apply them via class A,B primary. This ensures visual consistency and easier maintenance.

5. Poor AI Prompt Structure for Generation

Explanation: Vague prompts like "draw a diagram of this code" produce inconsistent syntax, missing participants, or invalid Mermaid blocks. LLMs require explicit output formatting and structural constraints. Fix: Always specify diagram type, participant naming rules, error path inclusion, and output format. Example: "Output only a valid mermaid sequence diagram. Use participant aliases. Include alt blocks for validation failures. Do not include markdown text outside the code block."

6. Markdown Syntax Conflicts

Explanation: Mermaid blocks inside markdown can break if the surrounding content uses conflicting delimiters. Triple backticks inside a mermaid block, or unescaped colons in labels, cause parser errors. Fix: Use consistent markdown fencing. Escape special characters in node labels using quotes: A["Order: #1024"]. Test rendering in the target platform before merging.

7. Assuming Cross-Platform Parity

Explanation: GitHub, GitLab, VS Code, and Notion use different Mermaid versions and renderer configurations. A diagram that renders perfectly in VS Code may break on GitHub due to version drift or CSS overrides. Fix: Target the lowest common denominator (usually GitHub's renderer). Avoid experimental syntax. Pin Mermaid versions in local preview tools. Validate diagrams in the primary consumption platform before release.

Production Bundle

Action Checklist

  • Define a naming convention for node IDs and participant aliases across the repository
  • Create a centralized Mermaid theme configuration for consistent styling
  • Split architectures exceeding 50 nodes into focused, linked diagrams
  • Validate all diagrams in the primary consumption platform (GitHub/GitLab) before merging
  • Implement a CI step to lint mermaid blocks using mermaid-cli or a markdown linter
  • Document AI prompt templates for consistent diagram generation across the team
  • Add cross-references between related diagrams to maintain navigability
  • Review diagram diffs during pull requests alongside code changes

Decision Matrix

ScenarioRecommended ApproachWhyCost Impact
System topology & component relationshipsFlowchart (graph TD/LR)Best for static architecture, bounded contexts, and deployment boundariesLow (fast to generate, easy to maintain)
API contracts & runtime interactionsSequence DiagramCaptures temporal flow, async events, and error paths accuratelyMedium (requires precise participant mapping)
Database schema & entity relationshipsClass DiagramModels attributes, relationships, and inheritance clearlyLow (directly maps to ORM/SQL structures)
Project timelines & sprint planningGantt ChartVisualizes dependencies, milestones, and resource allocationMedium (requires external data sync for accuracy)
Decision logic & branching workflowsState DiagramTracks lifecycle transitions and guard conditionsLow (ideal for finite state machines)

Configuration Template

VS Code Settings (settings.json)

{
  "markdown.preview.markEditorScrollSync": true,
  "markdown.preview.markPreviewScrollSync": true,
  "markdown.extension.mermaid.renderingMethod": "mermaid-cli",
  "mermaid.config": {
    "theme": "default",
    "themeVariables": {
      "primaryColor": "#e3f2fd",
      "primaryTextColor": "#1a237e",
      "primaryBorderColor": "#1976d2",
      "lineColor": "#546e7a",
      "secondaryColor": "#f5f5f5",
      "tertiaryColor": "#fff"
    },
    "flowchart": {
      "curve": "basis",
      "padding": 20
    },
    "sequence": {
      "mirrorActors": false,
      "bottomMarginAdj": 1
    }
  }
}

GitHub Actions Workflow (Diagram Validation)

name: Validate Mermaid Diagrams
on:
  pull_request:
    paths:
      - '**/*.md'

jobs:
  lint-diagrams:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Install Mermaid CLI
        run: npm install -g @mermaid-js/mermaid-cli
      - name: Extract & Validate Diagrams
        run: |
          find . -name "*.md" -exec grep -l '```mermaid' {} \; | while read file; do
            echo "Validating $file"
            # Extract mermaid blocks and pipe to mmdc for syntax validation
            sed -n '/```mermaid/,/```/p' "$file" | grep -v '```' | mmdc -i /dev/stdin -o /dev/null 2>&1 || exit 1
          done

Quick Start Guide

  1. Create a markdown file in your repository (e.g., docs/architecture.md).
  2. Insert a mermaid code block with the language tag:
    ```mermaid
    graph LR
        A[Client] --> B[API Gateway]
        B --> C[Service Layer]
    
  3. Preview locally using VS Code's built-in markdown preview (Ctrl+Shift+V) or the Mermaid extension.
  4. Commit and push. GitHub/GitLab will automatically render the diagram in the repository view.
  5. Iterate with AI. Paste source code into your LLM with the prompt template from Step 4, paste the output into a new mermaid block, and validate.