Drawing system architecture diagrams with Mermaid
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
| Tool | When to use |
|---|---|
| Mermaid | Living documentation in git; diagrams that change with code |
| draw.io / Lucidchart | One-time diagrams; highly polished presentations |
| C4 Model tools | Formal 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
Flowchart vs sequence diagram: when to use each in Mermaid
Both show process flows, but flowcharts model decisions and branches while sequence diagrams trace interactions over time. Here's how to pick the right one.
Visualizing metrics with Mermaid pie and bar chart diagrams
Use Mermaid's pie and bar chart syntax to show proportions and trends—no external charting library needed. A complete guide with examples.