Data model
This page explains the collections behind the book-sharing system and the role each one plays.
The model is intentionally split into catalog data, member activity, and interaction data. That separation reduces duplication and makes it easier to apply different rules to each layer.
Core collections
| Collection | Purpose | Key fields | Notes |
|---|---|---|---|
authors |
Canonical author records | name, slug, bio, photo, status |
Linked to books through book_authors |
books |
Canonical book records | title, slug, description, isbn, status |
Main public content target for reading journeys and book interactions |
book_authors |
Junction table for book-author links | books_id, authors_id, sort |
M2M bridge between books and authors |
reading_journeys |
A member's longer-running reading record | title, slug, status, visibility, book, user |
Tied to one book and one user |
reading_shares |
A member's share post about reading activity | slug, status, visibility, content, book, reading_journey, user |
Can exist with or without a linked journey |
reading_share_images |
Junction table for share images | reading_share_id, directus_files_id, sort |
M2M bridge between shares and uploaded files |
Why the core model looks like this
authorsandbooksare canonical records because the same book should not be recreated for every member.book_authorsexists as a junction because books can have multiple authors, and authors can write multiple books.reading_journeysexists separately fromreading_sharesbecause long-lived progress tracking and short-form publishing are different behaviors.reading_share_imagesis kept separate so a share can have many images without bloating the main share record.
Interaction collections
| Collection | Type | Target field | Owner field | Main fields |
|---|---|---|---|---|
reading_share_comments |
Comment | reading_share |
user |
status, visibility, content |
reading_share_reactions |
Reaction | reading_share |
user |
date_created |
reading_journey_comments |
Comment | reading_journey |
user |
status, content |
reading_journey_reactions |
Reaction | reading_journey |
user |
date_created |
book_comments |
Comment | book |
user |
status, content |
book_reactions |
Reaction | book |
user |
date_created |
Why interaction data is modeled separately
- Comments are their own collections because they need moderation fields and their own ownership rules.
- Reactions are their own collections because they are conceptually link rows, not embedded counters.
- Book, journey, and share interactions are split by target type because each target has different visibility and write rules.
This is a little more verbose than a single polymorphic interaction table, but it is much easier to understand and safer to validate in Directus.
Relationship patterns
| Pattern | Example | Why it matters |
|---|---|---|
| One book to many journeys | reading_journeys.book -> books |
A journey is always about one book |
| One book to many shares | reading_shares.book -> books |
A share always points back to one book |
| One journey to many shares | reading_shares.reading_journey -> reading_journeys |
A share can be part of a longer reading journey |
| One target to many comments | book_comments.book -> books |
Comments stay close to a single content target |
| One target to many reactions | reading_share_reactions.reading_share -> reading_shares |
Reactions are simple link rows, not rich content |
| Many books to many authors | books <-> authors via book_authors |
Supports co-authored works cleanly |
These relationship patterns reflect how people actually use the system.
- A member reads one specific book in one journey.
- A share is usually about one reading moment.
- Comments and reactions need an unambiguous target.
The model favors clarity over cleverness. That makes downstream queries, moderation, and UI work simpler.
Ownership and audit fields
There are two separate concepts in play.
| Field family | Meaning | Used in |
|---|---|---|
user |
Business owner of the content or interaction | reading_journeys, reading_shares, comments, reactions |
user_created, user_updated, date_created, date_updated |
Directus audit metadata | Most editable content collections |
For comments and reactions, user is the field the product actually cares about. Directus flows now protect that field from spoofing.
This is an important design choice. Audit fields tell us who created or updated a record inside Directus. The business user field tells us who the content belongs to in the product. Those are not always the same concern, so the model keeps them separate.
Entity view
erDiagram
AUTHORS ||--o{ BOOK_AUTHORS : linked_by
BOOKS ||--o{ BOOK_AUTHORS : linked_by
BOOKS ||--o{ READING_JOURNEYS : has
BOOKS ||--o{ READING_SHARES : has
READING_JOURNEYS ||--o{ READING_SHARES : groups
READING_SHARES ||--o{ READING_SHARE_COMMENTS : has
READING_SHARES ||--o{ READING_SHARE_REACTIONS : has
READING_JOURNEYS ||--o{ READING_JOURNEY_COMMENTS : has
READING_JOURNEYS ||--o{ READING_JOURNEY_REACTIONS : has
BOOKS ||--o{ BOOK_COMMENTS : has
BOOKS ||--o{ BOOK_REACTIONS : has
READING_SHARES ||--o{ READING_SHARE_IMAGES : has
Design choices worth noticing
- Reaction collections are intentionally minimal. They are just target-plus-user rows with a timestamp.
- Comment collections keep richer moderation fields like
status, but reaction collections do not. - All interaction collections use UUID primary keys, even when the business rule is effectively a unique pair.
- User relations on comments and reactions are nullable so content can survive user deletion.
The overall design is biased toward explicit models.
- More collections, but simpler rules inside each one.
- More relations, but cleaner query behavior.
- More structure, but less ambiguity about what a row means.