Entity Relationship Diagrams with Mermaid.js
Design database schemas with Mermaid.js ER diagrams. Learn entities, attributes, relationships, cardinality, and best practices for data modeling.
What Are ER Diagrams?
Entity Relationship Diagrams (ERDs) visualize database structure — the tables (entities), their columns (attributes), and how they relate to each other. They're essential for:
- Designing new databases
- Documenting existing schemas
- Communicating data models to team members
- Planning migrations and refactors
Mermaid's ER diagram syntax lets you create these diagrams as code, keeping your data documentation in sync with your actual schema.
Basic Syntax
erDiagram
CUSTOMER {
int id PK
string name
string email
}
ORDER {
int id PK
date created_at
int customer_id FK
}
CUSTOMER ||--o{ ORDER : placesTry in Editor →Key elements:
- Entities are defined with their name and attributes in curly braces
- Attributes have a type, name, and optional constraint (PK, FK, UK)
- Relationships use special notation for cardinality
Attribute Syntax
Each attribute follows the pattern: type name constraint "comment"
erDiagram
USER {
int id PK "Auto-increment primary key"
string username UK "Unique username"
string email UK "Unique email address"
string password_hash "Bcrypt hashed"
datetime created_at "Default: now()"
datetime updated_at "Updated on change"
boolean is_active "Default: true"
}Try in Editor →Supported Constraints
- PK — Primary Key
- FK — Foreign Key
- UK — Unique Key
Relationship Cardinality
Mermaid uses a notation combining two symbols — one for each end:
erDiagram
A ||--|| B : "one to one"
C ||--o{ D : "one to many"
E }o--o{ F : "many to many"
G ||--o| H : "one to zero or one"Try in Editor →Notation Guide
Left/right symbols:
- || — exactly one
- o| — zero or one
- }| — one or more
- }o — zero or more
The line style:
- -- — solid line (identifying relationship)
- .. — dashed line (non-identifying relationship)
Common Patterns
erDiagram
%% One-to-many (most common)
AUTHOR ||--o{ BOOK : writes
%% Many-to-many (via junction table)
STUDENT }o--o{ COURSE : enrolls_in
%% One-to-one
USER ||--|| PROFILE : has
%% Optional relationship
EMPLOYEE ||--o| PARKING_SPOT : assignedTry in Editor →Practical Example: Blog Platform
erDiagram
USER {
int id PK
string username UK
string email UK
string password_hash
string avatar_url
text bio
datetime created_at
}
POST {
int id PK
string title
string slug UK
text content
string status "draft|published|archived"
int author_id FK
datetime published_at
datetime created_at
datetime updated_at
}
COMMENT {
int id PK
text body
int post_id FK
int author_id FK
int parent_id FK "For nested comments"
datetime created_at
}
TAG {
int id PK
string name UK
string slug UK
}
POST_TAG {
int post_id FK
int tag_id FK
}
LIKE {
int id PK
int user_id FK
int post_id FK
datetime created_at
}
USER ||--o{ POST : writes
USER ||--o{ COMMENT : writes
POST ||--o{ COMMENT : has
COMMENT ||--o{ COMMENT : replies_to
POST ||--o{ POST_TAG : has
TAG ||--o{ POST_TAG : tagged
USER ||--o{ LIKE : gives
POST ||--o{ LIKE : receivesTry in Editor →Practical Example: E-Commerce Database
erDiagram
CUSTOMER {
int id PK
string first_name
string last_name
string email UK
string phone
datetime created_at
}
ADDRESS {
int id PK
int customer_id FK
string street
string city
string state
string zip_code
string country
boolean is_default
}
PRODUCT {
int id PK
string name
string sku UK
text description
decimal price
int stock_quantity
int category_id FK
boolean is_active
}
CATEGORY {
int id PK
string name
int parent_id FK "Self-referencing for hierarchy"
}
ORDERS {
int id PK
int customer_id FK
int shipping_address_id FK
string status
decimal subtotal
decimal tax
decimal total
datetime ordered_at
}
ORDER_ITEM {
int id PK
int order_id FK
int product_id FK
int quantity
decimal unit_price
decimal subtotal
}
PAYMENT {
int id PK
int order_id FK
string method
string status
decimal amount
string transaction_id UK
datetime paid_at
}
CUSTOMER ||--o{ ADDRESS : has
CUSTOMER ||--o{ ORDERS : places
ORDERS ||--|{ ORDER_ITEM : contains
ORDERS ||--|| PAYMENT : paid_by
ORDERS }o--|| ADDRESS : ships_to
ORDER_ITEM }o--|| PRODUCT : references
PRODUCT }o--|| CATEGORY : belongs_to
CATEGORY ||--o{ CATEGORY : parent_ofTry in Editor →Practical Example: SaaS Multi-Tenant
erDiagram
TENANT {
int id PK
string name
string subdomain UK
string plan "free|pro|enterprise"
datetime created_at
}
USER {
int id PK
int tenant_id FK
string email
string role "admin|member|viewer"
datetime last_login
}
PROJECT {
int id PK
int tenant_id FK
string name
text description
int owner_id FK
}
TASK {
int id PK
int project_id FK
string title
text description
string status "todo|doing|done"
int assignee_id FK
date due_date
}
TENANT ||--o{ USER : has
TENANT ||--o{ PROJECT : owns
USER ||--o{ PROJECT : manages
PROJECT ||--o{ TASK : contains
USER ||--o{ TASK : assignedTry in Editor →Tips for Data Modeling with Mermaid
1. Start with Entities
List your main entities first without attributes. Map out the relationships. Then add attributes.
2. Name Relationships Clearly
The label after the colon describes the relationship: CUSTOMER ||--o{ ORDER : places. Use active verbs: "places", "contains", "belongs_to", "writes".
3. Show Junction Tables
For many-to-many relationships, explicitly show the junction table:
erDiagram
STUDENT }o--o{ COURSE : enrolls_inTry in Editor →Or with the junction table visible:
erDiagram
STUDENT ||--o{ ENROLLMENT : has
COURSE ||--o{ ENROLLMENT : has
ENROLLMENT {
int student_id FK
int course_id FK
date enrolled_at
string grade
}Try in Editor →The second approach is better when the junction table has its own attributes.
4. Include Data Types
Always specify types — it makes the diagram useful for actual implementation:
- int, bigint — IDs, counts
- string or varchar — text fields
- text — long content
- decimal — money, precise numbers
- boolean — flags
- datetime, date, timestamp — temporal data
- json or jsonb — flexible structured data
5. Mark Constraints
Always mark PK, FK, and UK. These are the most important structural information in a schema diagram.
6. Use Comments for Business Rules
Add comments in quotes to document constraints that aren't visible in the structure:
erDiagram
ORDER {
string status "Enum: pending|processing|shipped|delivered|cancelled"
decimal total "Computed: sum of line items + tax"
}Try in Editor →Common Mistakes
- Missing foreign keys — Every relationship line should have a corresponding FK attribute.
- Wrong cardinality — Think carefully: can a customer have zero orders? Then it's
||--o{not||--|{. - Tables without relationships — If an entity has no relationships, it might not belong in the diagram, or you're missing a connection.
- Too many entities — Focus on one bounded context. A diagram with 30+ tables is overwhelming.
Conclusion
Mermaid ER diagrams are perfect for documenting database schemas in your project repository. They render on GitHub, stay version-controlled, and are easy to update when your schema evolves. Start with the core entities and relationships, then progressively add attributes and constraints as your model matures.