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.โ



Join the discussion
Sign in with your account to post comments, reply to others, and participate in the conversation.
Login to Comment