Class and ER Diagrams for Schemas
Two related modeling tools in one sitting. classDiagram for object structure — attributes, methods, visibility, and relationships (inheritance, composition, association). erDiagram for database schemas — entities, attributes, primary/foreign keys, and crow's-foot cardinality (one-to-many, many-to-many). When to reach for which, and how to keep both honest as the model changes.
Class and ER Diagrams for Schemas
Quick orientation: classDiagram models object structure — class attributes, methods, visibility, and type relationships in code. erDiagram models database schemas — tables, columns, primary/foreign keys, and crow's-foot cardinality. The same system often needs both: your User class might expose a logout() method the database has no concept of, while the users table carries created_at columns the domain model treats as plumbing. Don't force one diagram to do both jobs.
classDiagram
Keyword is classDiagram — no direction argument, layout is automatic. Define members inside curly braces or with colon notation:
classDiagram
class User {
+int id
+String email
-String passwordHash
+login() bool
+logout()
}The parser spots methods by () — no parentheses means attribute. Return types trail the closing paren: login() bool.
Visibility modifiers:
| Symbol | Meaning |
|---|---|
+ | public |
- | private |
# | protected |
~ | package/internal |
Append $ for static and * for abstract: getInstance()$, validate()*.
Relationship types:
| Arrow | Type | Reads as |
|---|---|---|
| `A <\ | -- B` | Inheritance |
A *-- B | Composition | A owns B; B can't exist alone |
A o-- B | Aggregation | A has B; B lives independently |
A --> B | Association | A references B |
| `A <\ | .. B` | Realization |
A <.. B | Dependency | B depends on A |
Arrowhead points toward the parent: Animal <|-- Dog means Dog inherits from Animal.
Add multiplicity labels in quotes and a verb label after a colon:
Order "1" --> "*" LineItem : containsMark a class as an interface with a stereotype inside the class block:
class Publishable {
<<interface>>
publish()
unpublish()
}classDiagram
class Publishable {
<<interface>>
publish()
unpublish()
}
class User {
+int id
+String email
-String passwordHash
+login() bool
+logout()
}
class Post {
+int id
+String title
+String body
+bool published
+publish()
+unpublish()
}
class Comment {
+int id
+String body
+timestamp createdAt
}
Publishable <|.. Post : realizes
User "1" --> "*" Post : authors
Post "1" *-- "*" Comment : owns---
erDiagram
Switch keyword to erDiagram. Entity names are conventionally uppercase. Attribute blocks use type name [constraint] syntax:
erDiagram
USER {
int id PK
string email UK
string password_hash
}
ORDER {
int id PK
int user_id FK
timestamp created_at
}
USER ||--o{ ORDER : "places"Supported constraint tags: PK, FK, UK. Combine when needed (int id PK, FK) and add a trailing comment in double quotes: int id PK "auto-increment".
Cardinality is where most people stumble. Each relationship token has three parts:
ENTITY_A [left-marker][line][right-marker] ENTITY_B : "label"Line type: -- for identifying (solid), .. for non-identifying (dashed).
Cardinality markers:
| Symbol | Meaning |
|---|---|
| `\ | \ |
| `\ | o / o\ |
| `}\ | / \ |
}o / o{ | Zero or more |
Read each side independently. USER ||--o{ ORDER : "places" — || on the User side (exactly one), o{ on the Order side (zero or more). One User places zero or more Orders.
The label after : is required — omitting it causes a parse error. Quote any label with spaces.
erDiagram
USER {
int id PK
string email UK
string password_hash
}
POST {
int id PK
int user_id FK
string title
text body
boolean published
}
COMMENT {
int id PK
int post_id FK
int user_id FK
text body
}
USER ||--o{ POST : "authors"
POST ||--o{ COMMENT : "has"
USER ||--o{ COMMENT : "writes"Test your cardinality recall before moving on:
---
Which one for which job?
- classDiagram: documenting code — domain models, service interfaces, library APIs.
- erDiagram: documenting storage — database schemas, migrations, data dictionaries.
They can and should coexist in the same docs. Different audiences, different jobs — don't force one to serve both.
---
Keeping both honest as the model changes
Diagrams drift. A few habits help:
1. Co-locate with the code they describe. An erDiagram lives in the same directory as your migrations. When a migration changes, the diagram is right there.
db/
├── migrations/
│ ├── 001_create_users.sql
│ └── 002_create_posts.sql
└── schema.md ← erDiagram lives here2. Keep entities flat. Show only columns worth explaining to a newcomer — audit timestamps and soft-delete flags can usually stay out.
3. Prefer two focused diagrams over one comprehensive one. When an entity grows past ~8 attributes, split into a relationship view and a full-attribute reference.
4. Use subgraphs in classDiagram to mark service boundaries — same technique as the flowchart submodule. Wrap related classes in a subgraph to show module ownership without drawing a line for every dependency.
5. Mermaid won't catch logical errors. You can write contradictory cardinality and it'll render both arrows without complaint. Cross-check tokens against your actual FK constraints before publishing.
---
Quick check
1. Write a classDiagram with Post, Author (one-to-many association), Comment (composition), and a Publishable interface that Post realizes.
2. Write the matching erDiagram with posts, users, and comments tables — mark PKs and FKs.
3. Express "a post has zero or more comments; a comment belongs to exactly one post" as a relationship token — write it from memory, then verify against the table.
State diagrams and Gantt charts are next — a shift from modeling structure to tracking flow over states and work over time.
Self-check
- You're documenting a system where `User` objects have a `logout()` method but the `users` database table has no `logout` column. Which statement best explains why you need both a `classDiagram` and an `erDiagram` rather than forcing one to cover both?
- In a `classDiagram`, you mark `password_hash` as `-String password_hash`. What does the leading `-` tell a reader about how this field should be accessed?
- The relationship `CUSTOMER ||--o{ ORDER : "places"` describes the cardinality between customers and orders. Which statement correctly interprets both markers?
- You're modeling a `Document` that contains `Page` objects. If deleting a document should automatically delete its pages, but individual pages can't be created without a document, which relationship type correctly captures this?
- In a `classDiagram`, how does Mermaid distinguish between an attribute and a method?
- You write `Animal <|-- Dog` to show that Dog inherits from Animal. Why does the arrow point toward `Animal` rather than toward `Dog`?
- Your team has been using an `erDiagram` to document the database schema, but a recent migration added columns that haven't been reflected in the diagram. Which practice from the article would most directly prevent this kind of drift?
- When a `classDiagram` member like `+login() bool` doesn't appear in the corresponding `erDiagram`, what does this most likely mean?
Homework
Class Diagram for an E-Commerce Product Catalogimage
You're documenting code for an e-commerce product catalog. Create a classDiagram modeling Product, Category, Review, and User classes. Show their attributes with types, at least 3 methods (with mixed visibility), and at least 2 relationship types. Include an interface somewhere if it makes sense.
- Renders without errors (syntactically valid Mermaid) - 4+ classes defined with attributes and types - 3+ methods with at least one return type, showing mixed visibility (+, -, #) - 2+ different relationship types using correct syntax (inheritance, composition, aggregation, association) - If an interface is used, properly marked with <<interface>>
ER Diagram for the Same Catalog (Database Schema)image
Now model the same catalog as a database schema. Create an erDiagram with at least 4 entities (tables) — corresponding to your classes but thinking in database terms. Mark primary keys (PK) and foreign keys (FK). Use crow's-foot cardinality notation for relationships and label every relationship. Don't force everything into the database model; some class attributes (methods, interfaces) stay in code only.
- Renders without errors (syntactically valid Mermaid) - 4+ entities with meaningful names corresponding to your classes - At least 2 primary keys and 2 foreign keys marked - 3+ relationships with correct crow's-foot cardinality markers (||, |o, }|, }o) - Every relationship has a label (no unlabeled relationships) - Logical consistency: FKs reference PKs, cardinality reflects the business logic from your class model
Reconciliation: Comparing Models and Explaining Divergencetext
Compare your two diagrams. Write 200–300 words analyzing: - At least 3 things in your class diagram that don't appear in the ER diagram, and why - At least 2 things in your ER diagram that don't appear in the class diagram, and why - One relationship shown in both notations, with its syntax in each - One real scenario where you'd update one diagram but not the other Be specific to your own diagrams, not generic.
- 200–300 words - Identifies 3+ class-diagram-only elements (e.g., methods, visibility modifiers, interfaces) with reasoning - Identifies 2+ ER-diagram-only elements (e.g., surrogate keys, audit columns, normalization) with reasoning - Compares one relationship in both notations, showing the actual syntax from each diagram - Provides a realistic scenario where one diagram changes without changing the other - References specific class/entity names and relationships from the learner's own work
Submit and get assignments reviewed in the Learn (Almost) Anything app.
