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 *


