All posts
MermaidArchitectureSystem DesignDocumentation

Drawing system architecture diagrams with Mermaid

6 min readThe MermaidCreator team

System architecture diagrams are how teams align on the shape of a system—which services talk to which, where data lives, how scaling happens. A good one prevents "wait, does the API call the worker or vice versa?" questions weeks into a project.

Mermaid's flowchart and graph syntax work well for architecture because they're flexible enough for services, queues, databases, and external systems, yet simple enough to maintain in your README or Architecture Decision Record (ADR).

Architecture diagram patterns

Most architecture diagrams fall into a few patterns:

1. Service mesh / microservices Show how services connect and what they exchange.

2. Data flow Trace where data originates, how it moves between systems, and where it lands.

3. Deployment topology Show what runs where: on-prem, cloud regions, Kubernetes clusters, edge.

4. Scaled/replicated components Illustrate horizontal scaling, redundancy, and load balancing.

Basic service architecture

Start with the simplest pattern—boxes for services, arrows for calls:

flowchart LR
    Client["🖥️ Client<br/>(Browser)"]
    LB["⚙️ Load Balancer"]
    API1["🔵 API Server 1"]
    API2["🔵 API Server 2"]
    Cache["⚡ Redis Cache"]
    DB[("🗄️ PostgreSQL")]
    Queue["📬 Job Queue<br/>(RabbitMQ)"]
    Worker["🔨 Background Worker"]
    
    Client -->|HTTPS| LB
    LB -->|HTTP| API1
    LB -->|HTTP| API2
    
    API1 -->|Query/Write| DB
    API2 -->|Query/Write| DB
    API1 & API2 -->|Check Cache| Cache
    
    API1 & API2 -->|Enqueue Job| Queue
    Queue -->|Dequeue| Worker
    Worker -->|Write| DB
    
    style Client fill:#f0f0f0
    style LB fill:#fff8e1
    style API1 fill:#c8e6c9
    style API2 fill:#c8e6c9
    style Cache fill:#ffe0b2
    style DB fill:#f8bbd0
    style Queue fill:#d1c4e9
    style Worker fill:#c8e6c9

This immediately tells you:

  • Clients don't talk to APIs directly; they go through a load balancer.
  • Both API instances read/write the same database.
  • The cache is shared.
  • Background jobs are decoupled via a queue.

Multi-region / high-availability architecture

Add complexity for production:

flowchart TD
    CDN["🌍 CDN<br/>(CloudFront)"]
    
    subgraph US["🇺🇸 US Region (us-east-1)"]
        ALB1["🔀 ALB"]
        API_US1["API Pod 1"]
        API_US2["API Pod 2"]
        RDS_US[("RDS Primary")]
    end
    
    subgraph EU["🇪🇺 EU Region (eu-west-1)"]
        ALB2["🔀 ALB"]
        API_EU1["API Pod 1"]
        RDS_EU[("RDS Replica")]
    end
    
    subgraph Data["📊 Shared Services"]
        DDB["DynamoDB<br/>(Global Table)"]
        S3["S3 Bucket<br/>(replicated)"]
    end
    
    CDN --> ALB1
    CDN --> ALB2
    
    ALB1 --> API_US1 & API_US2
    ALB2 --> API_EU1
    
    API_US1 & API_US2 --> RDS_US
    API_EU1 --> RDS_EU
    
    RDS_US -.->|Replication| RDS_EU
    
    API_US1 & API_US2 & API_EU1 -->|Read/Write| DDB
    API_US1 & API_US2 & API_EU1 -->|Upload| S3
    
    style CDN fill:#bbdefb
    style RDS_US fill:#ffccbc
    style RDS_EU fill:#ffccbc
    style DDB fill:#f0f4c3
    style S3 fill:#f0f4c3

This shows:

  • CDN routes traffic to two regions.
  • Each region has an ALB and scaled API pods.
  • US has a primary database; EU has a replica (replication shown with dotted line).
  • Shared global tables (DynamoDB, S3) are accessed by all.

Event-driven architecture

For systems built on async message passing:

flowchart LR
    Client["Client"]
    API["API Service"]
    
    subgraph Events["📨 Event Bus (Kafka)"]
        Topic1["user.created"]
        Topic2["order.placed"]
        Topic3["payment.received"]
    end
    
    Consumer1["User Notifier"]
    Consumer2["Analytics Collector"]
    Consumer3["Fulfillment Service"]
    Consumer4["Invoice Generator"]
    
    DB[(Database)]
    Analytics[("Data Warehouse")]
    Email["Email Service"]
    
    Client -->|POST /users| API
    API -->|Publish| Topic1
    API -->|Publish| Topic2
    
    Topic1 & Topic2 --> Consumer1
    Consumer1 --> Email
    
    Topic1 & Topic2 & Topic3 --> Consumer2
    Consumer2 --> Analytics
    
    Topic2 --> Consumer3
    Topic3 --> Consumer4
    Consumer4 --> Email
    
    API --> DB
    Consumer3 --> DB
    
    style API fill:#c8e6c9
    style Consumer1 fill:#ffe0b2
    style Consumer2 fill:#ffe0b2
    style Consumer3 fill:#ffe0b2
    style Consumer4 fill:#ffe0b2
    style Analytics fill:#f0f4c3
    style Email fill:#ffccbc

Key insight: the API publishes events; many consumers can subscribe independently. That's scalability and loose coupling.

Containerized / Kubernetes architecture

flowchart TD
    Ingress["Ingress<br/>(API Gateway)"]
    
    subgraph K8s["Kubernetes Cluster"]
        subgraph NS_API["api namespace"]
            Deploy1["Deployment: api<br/>3 replicas"]
            Deploy2["Deployment: scheduler<br/>1 replica"]
        end
        
        subgraph NS_DB["db namespace"]
            StatefulSet["StatefulSet: postgres<br/>1 primary + 2 replicas"]
        end
        
        ConfigMap["ConfigMap: app-config"]
        Secret["Secret: db-creds"]
    end
    
    extLB["External Load Balancer"]
    CloudStorage["Cloud Storage<br/>(S3)"]
    
    extLB --> Ingress
    Ingress --> Deploy1
    Deploy1 --> StatefulSet
    Deploy2 --> StatefulSet
    
    Deploy1 & Deploy2 -->|Read from| ConfigMap
    Deploy1 & Deploy2 -->|Auth via| Secret
    
    Deploy1 & Deploy2 -->|Logs| CloudStorage
    
    style Ingress fill:#bbdefb
    style Deploy1 fill:#c8e6c9
    style Deploy2 fill:#c8e6c9
    style StatefulSet fill:#f8bbd0
    style ConfigMap fill:#f0f4c3
    style Secret fill:#f0f4c3
    style CloudStorage fill:#e1bee7

This shows:

  • External traffic enters via Ingress.
  • API and scheduler pods scale independently.
  • Postgres is a StatefulSet (keeps identity and persistent storage).
  • Shared config and secrets are injected at runtime.

Data flow / ETL pipeline

Sometimes you care less about topology and more about how data moves:

flowchart LR
    Raw["📥 Raw Data<br/>(S3 bucket)"]
    Spark["⚙️ Spark Job<br/>(EMR)"]
    Cleaned["📊 Cleaned Data<br/>(S3)"]
    Warehouse["🏢 Redshift<br/>(Data Warehouse)"]
    BI["📈 BI Tool<br/>(Tableau)"]
    ML["🤖 ML Pipeline<br/>(SageMaker)"]
    
    Raw -->|Daily ingest| Spark
    Spark -->|Transform & validate| Cleaned
    Cleaned -->|Load| Warehouse
    Warehouse -->|Query| BI
    Warehouse -->|Training data| ML
    ML -->|Model artifacts| S3["🔹 S3"]
    
    style Raw fill:#ffccbc
    style Spark fill:#fff9c4
    style Cleaned fill:#c8e6c9
    style Warehouse fill:#bbdefb
    style BI fill:#e1bee7
    style ML fill:#f0f4c3

Tips for architecture diagrams

Keep it at one level of abstraction. Don't mix "API servers" with "individual functions"—pick services or pods, not both in the same diagram.

Use color coding. Assign colors by tier (green for compute, blue for databases, orange for external). The MermaidCreator editor lets you style nodes interactively.

Label connections. Show what travels: HTTP, gRPC, SQL queries, async messages. API -->|JSON| DB is clearer than just API --> DB.

Dashed lines for secondary flows. Use -.-> for replication, backups, or optional flows. Solid arrows are the happy path.

One diagram per audience. A full architecture is messy; break it into:

  • For engineers: all components, all connections.
  • For stakeholders: major services, data residency, uptime.
  • For incident response: just the critical path.

When to use Mermaid vs. draw.io / Lucidchart

ToolWhen to use
MermaidLiving documentation in git; diagrams that change with code
draw.io / LucidchartOne-time diagrams; highly polished presentations
C4 Model toolsFormal architecture frameworks; large organizations

For engineering teams, Mermaid is unbeatable—it lives in your repo, diffs clearly, and doesn't rot behind a login.

FAQ

Q: Should I include every service in the architecture? A: No. Show the main path and 1–2 alternate paths. Hide internal retry loops, health checks, and logging sidecars unless they're critical to the architecture story.

Q: How do I show disaster recovery or failover? A: Use two side-by-side diagrams (normal state and after failover) or annotate with -- Failover: X takes over --.

Q: Can I show autoscaling? A: Yes—label a deployment "3–10 replicas" or use Deployment: api<br/>(scaled 0–100). Text is your friend.

Q: How do I diagram a serverless / FaaS architecture? A: Treat each Lambda/Cloud Function as a small box; group by flow. The same principles apply—show inputs, outputs, and data movement.


Ready to diagram your system? Start with a simple sketch in the MermaidCreator playground, then move it into your ADR or architecture doc.

Related posts