Let's design the database schema for the platform.
Choosing the Database
For a platform like this, the ideal choice is PostgreSQL, because:
- It supports relational data modeling (users <-> posts <-> subscriptions).
- It's ACID-compliant β ensuring reliability even during concurrent writes.
- It offers full-text search, JSON support, and great indexing options.
ORM layer: Prisma (for Node.js)
Core Entities Overview
Before diving into tables, let's identity the main entities and their relationships.
| Entity | Description |
|---|---|
| User | Represents both creators and readers |
| Post | A blog/newsletter entry written by a creator |
| Subscription | Tracks which reader follows which creator |
| Payment | Records transactions and plans |
| EmailQueue | Manages pending newsletters |
| Analytics | Stores engagement metrics (views, clicks, etc.) |
| Comment | Reader interaction with posts |
| Tag | Categorizes posts for discoverability |
Entity-Relationship (ER) Diagram
ββββββββββββββββ
β User β
ββββββββββββββββ
β id (PK) β
β name β
β email β
β role β
β password_hash β
β created_at β
βββββββ¬βββββββββ
β1
β
ββ
βββββββΌβββββββββ
β Post β
βββββββββββββββββ
β id (PK) β
β author_id (FK)β
β title β
β content β
β status β
β created_at β
βββββββ¬βββββββββ
β
ββ
βββββββΌβββββββββ
β Comment β
ββββββββββββββββ
β id (PK) β
β post_id (FK) β
β user_id (FK) β
β content β
β created_at β
ββββββββββββββββ
ββββββββββββββββ
β Subscription β
ββββββββββββββββ
β id (PK) β
β reader_id FK β
β creator_id FKβ
β plan_type β
β is_active β
ββββββββββββββββ
ββββββββββββββββ
β Payment β
ββββββββββββββββ
β id (PK) β
β subscription_id (FK)β
β amount β
β currency β
β status β
β created_at β
ββββββββββββββββ
ββββββββββββββββ
β EmailQueue β
ββββββββββββββββ
β id (PK) β
β post_id (FK) β
β recipient_id β
β status β
β sent_at β
ββββββββββββββββ
Detailed Table Design
1 User Table
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| name | VARCHAR(100) | User name |
| VARCHAR(150) | Unique | |
| password_hash | TEXT | Securely stored password |
| role | ENUM('reader','creator','admin') | Role type |
| bio | TEXT | Creator bio |
| profile_image | TEXT | URL to avatar |
| created_at | TIMESTAMP | Default now() |
Each user can be both creator and a reader depending on context.
2 Post Table
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| author_id | UUID (FK) | References user.id |
| title | VARCHAR(255) | Post title |
| slug | VARCHAR(255) | URL-friendly ID |
| content | TEXT | Body content |
| visibility | ENUM('public','paid','draft') | Visibility level |
| created_at | TIMESTAMP | Created time |
| updated_at | TIMESTAMP | Last updated |
| published_at | TIMESTAMP | Publish date |
3 Comments Table
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| post_id | UUID (FK) | References post.id |
| user_id | UUID (FK) | References user.id |
| content | TEXT | Comment text |
| created_at | TIMESTAMP | Commented time |
4 Subscription Table
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| reader_id | UUID (FK) | References user.id |
| creator_id | UUID (FK) | References user.id |
| plan_type | ENUM('free','paid') | Subscription level |
| start_date | TIMESTAMP | Start time |
| end_date | TIMESTAMP | Renewal/cancel date |
| is_active | BOOLEAN | Current status |
5 Payment Table
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| subscription_id | UUID (FK) | References subscription.id |
| amount | DECIMAL(10,2) | Amount paid |
| currency | VARCHAR(10) | e.g. USD, INR |
| stripe_txn_id | VARCHAR(255) | External transaction |
| status | ENUM('success','failed','pending') | Payment status |
| created_at | TIMESTAMP | Payment date |
6 EmailQueue Table
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| post_id | UUID (FK) | References post.id |
| recipient_id | UUID (FK) | References user.id |
| status | ENUM('pending','sent','failed') | Delivery status |
| sent_at | TIMESTAMP | Sent timestamp |
This helps implement asynchronous email dispatch β ensuring reliability and retry logic.
7 Analytics Table
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| post_id | UUID (FK) | References post.id |
| views | INT | Number of views |
| clicks | INT | Click count |
| email_opens | INT | Email open count |
| created_at | TIMESTAMP | Time recorded |
8 Tag Table
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| name | VARCHAR(50) | Tag name |
| slug | VARCHAR(50) | URL-friendly tag |
| created_at | TIMESTAMP | Creation date |
and a PostTag junction table (many-to-many):
| post_id (FK) | tag_id (FK) |
Relationship Summary
| Relationship | Type | Description |
|---|---|---|
| User β Post | 1-to-many | Creator writes many posts |
| User β Subscription | Many-to-many | Readers subscribe to creators |
| Post β Comment | 1-to-many | Post has multiple comments |
| Post β Tag | Many-to-many | Posts have multiple tags |
| Subscription β Payment | 1-to-many | Each subscription has many payments |
| Post β EmailQueue | 1-to-many | Each post triggers multiple emails |
Indexing & Optimization
| Table | Indexed Columns | Purpose |
|---|---|---|
| User | Fast login lookups | |
| Post | slug, author_id | Quick post retrieval |
| Subscription | reader_id, creator_id | Fast subscription checks |
| Payment | stripe_txn_id | Payment reconciliation |
| EmailQueue | status | Queue processing efficiency |
Additional Optimizations:
- Use partial indexes for active subscriptions only.
- Add foreign key constraints for referential integrity.
- Enable caching for popular posts (via Redis).
Data Flow Example β βUser Subscribes to Creatorβ
[User clicks Subscribe]
β
βΌ
Frontend β Backend (Subscription Service)
β
βΌ
Stripe Checkout β Payment Verified
β
βΌ
Backend updates:
- Create subscription record
- Log payment
- Send email confirmation
Reflection
A strong schema is like a well-organized library β every book (post), reader, and transaction has its place.
This database design ensures:
- Fast retrieval of posts and subcriptions,
- Consistent data across users and payments.
- Extensibility for future modules (plugins, AI summaries, comments, etc.)
βDatabase is like long-term memory β design it like you will read from it for years.β
Leave a comment
Your email address will not be published. Required fields are marked *
