Changelog management platform with JWT authentication, workspace isolation, auto-slug generation, and RESTful APIs. Built with Express & MongoDB.
Transform the way you communicate product updates. ChangelogHub is a developer-friendly platform designed to help teams publish and manage their product changelogs seamlessly. Built with a focus on security, scalability, and clean architecture.
- Overview
- Key Features
- Tech Stack
- Project Structure
- Getting Started
- API Reference
- Authentication
- Database Models
- Configuration
- Development
- Contributing
- License
ChangelogHub is a full-stack changelog management platform that simplifies how teams communicate product updates to their users. Whether you're launching new features, fixing bugs, or improving your product, ChangelogHub provides a centralized, easy-to-use platform with:
- Secure Authentication using JWT tokens with refresh token rotation
- Multi-Tenant Support with workspace isolation
- Professional Changelog Management with versioning and status tracking
- Auto-Generated SEO-Friendly URLs using intelligent slug generation
- RESTful API for seamless integration
- Modern Frontend built with React and Vite
- User Registration with secure password hashing (bcryptjs)
- JWT-Based Authentication with access and refresh tokens
- Refresh Token Rotation for enhanced security
- Social Login with GitHub OAuth integration
- HttpOnly Cookies for secure token storage
- Automatic Workspace Creation on user signup
- Protected Routes with middleware-based JWT verification
- Secure Logout with token invalidation
- Workspace Isolation - Each user gets an isolated workspace with unique subdomain mapping
- Professional API Architecture - Standardized
ApiErrorandApiResponsewrappers for consistent data delivery - Secure Middleware Layer - Centralized error handling, auth verification, and request sanitization
- Multi-Tenant Support - Public pages dynamically fetch content based on the workspace identifier
- Immersive Hero Experience - Scroll-driven GSAP animations and parallax effects
- Modern UI Patterns - Sleek dark mode with glassmorphism and subtle micro-animations
- Premium Iconography - Custom SVG icon system replacing generic placeholders
- Responsive Dashboard - Feature-rich dashboard for managing releases, team, and subscribers
- Rich Text Editing - Integrated TipTap editor for professional product updates
- Draft & Publish Workflow - Versioned releases with SEO-friendly auto-generated slugs
- Subscriber CRM - Manage your audience with advanced filtering, sorting, and subscription tracking
- Dynamic Public Pages - Automatically generated public changelog pages for every workspace
| Technology | Purpose |
|---|---|
| Node.js | JavaScript runtime environment |
| Express.js | Fast, unopinionated web framework |
| MongoDB | NoSQL database for flexible data modeling |
| Mongoose | ODM for MongoDB with schema validation |
| JWT | Secure stateless authentication |
| Bcryptjs | Password hashing and verification |
| Helmet | HTTP header security |
| CORS / Cookie Parser | Cross-origin and session management |
| Cloudinary | Secure image hosting and management |
| GitHub OAuth | Social authentication integration |
| Technology | Purpose |
|---|---|
| React 19 | Modern library for building user interfaces |
| Vite | High-performance build tool |
| GSAP | High-fidelity animations and motion design |
| Tailwind CSS | Utility-first styling with custom tokens |
| TipTap | Advanced rich text editing framework |
| Lucide React | Clean, consistent icon set |
| React Router | Seamless client-side navigation |
| Axios | Promise-based HTTP client |
| Tool | Purpose |
|---|---|
| Nodemon | Auto-restart during development |
| Prettier | Code formatting |
| ESLint | Code linting and quality |
changelog-hub/
├── backend/ # Express.js backend
│ ├── src/
│ │ ├── app.js # Express app configuration
│ │ ├── server.js # Server entry point
│ │ ├── constant.js # Application constants
│ │ ├── controllers/ # Request handlers (auth, releases, etc.)
│ │ ├── models/ # MongoDB schemas (User, Workspace, Release)
│ │ ├── routes/ # API route definitions
│ │ │ ├── user.routes.js # Authentication endpoints
│ │ │ ├── release.routes.js # Release management endpoints
│ │ │ ├── public.routes.js # Public endpoints
│ │ │ └── subscriber.routes.js # Subscriber management
│ │ ├── middlewares/ # Custom middleware (auth, validation)
│ │ ├── db/ # Database connection
│ │ └── utils/ # Utility functions & helpers
│ ├── package.json
│ ├── .env.example
│ └── .prettierrc
│
├── frontend/ # React + Vite frontend
│ ├── src/
│ │ ├── components/ # Reusable React components
│ │ │ ├── RichTextEditor/ # Modular TipTap components
│ │ │ └── ErrorBoundary.jsx # Application error handling
│ │ ├── pages/ # Page components
│ │ │ ├── dashboard/ # Dashboard page & components
│ │ │ │ └── components/ # Header, Sidebar, MetricCard elements
│ │ │ └── releases/ # Release management pages
│ │ │ ├── components/ # Table, Pagination, Badge elements
│ │ │ └── hooks/ # Page-specific hooks (useReleaseForm)
│ │ ├── hooks/ # Global React hooks (useTooltip, useAuth)
│ │ ├── services/ # API service layer (auth, releases)
│ │ ├── stores/ # State management
│ │ ├── styles/ # Global styles (index.css)
│ │ ├── utils/ # Shared utility functions
│ │ └── App.jsx # Main app component
│ ├── index.html
│ ├── vite.config.js
│ ├── package.json
│ └── tailwind.config.js
│
├── docs/ # Documentation
├── .gitignore
├── LICENSE
└── README.md # This file
- Node.js (v16 or higher)
- npm or yarn
- MongoDB (local or Atlas)
- Git
git clone https://github.com/amar-295/changelog-hub.git
cd changelog-hubcd backend
# Install dependencies
npm install
# Create .env file from example
cp .env.example .env
# Configure environment variables (see Configuration section below)
nano .env
# Start development server
npm run dev
# Or start production server
npm startcd ../frontend
# Install dependencies
npm install
# Start development server with Vite
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview- Frontend:
http://localhost:5173(Vite dev server) - Backend API:
http://localhost:5000 - Health Check:
http://localhost:5000/
http://localhost:5000/api/v1
POST /auth/register
Content-Type: application/json
{
"username": "john_doe",
"email": "john@example.com",
"fullName": "John Doe",
"password": "SecurePassword123!"
}Response (201 Created):
{
"statusCode": 201,
"data": {
"_id": "507f1f77bcf86cd799439011",
"username": "john_doe",
"email": "john@example.com",
"fullName": "John Doe",
"role": "owner",
"workspaceId": "507f1f77bcf86cd799439012",
"createdAt": "2026-03-01T10:30:00Z",
"updatedAt": "2026-03-01T10:30:00Z"
},
"message": "User registered successfully",
"success": true
}POST /auth/login
Content-Type: application/json
{
"email": "john@example.com",
"password": "SecurePassword123!"
}Response (200 OK):
{
"statusCode": 200,
"data": {
"user": {
"_id": "507f1f77bcf86cd799439011",
"username": "john_doe",
"email": "john@example.com",
"fullName": "John Doe",
"workspaceId": "507f1f77bcf86cd799439012",
"lastLoginAt": "2026-03-01T10:35:00Z"
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"message": "User logged in successfully",
"success": true
}Note: Tokens are automatically set in httpOnly cookies for security.
POST /auth/refresh-token
Content-Type: application/json
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Response (200 OK):
{
"statusCode": 200,
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"message": "Access token refreshed successfully",
"success": true
}POST /auth/logout
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Response (200 OK):
{
"statusCode": 200,
"data": {},
"message": "User logged out successfully",
"success": true
}POST /releases
Authorization: Bearer {accessToken}
Content-Type: application/json
{
"title": "Dark Mode Support",
"content": "Added dark mode toggle to the dashboard for better accessibility",
"version": "v2.1.0",
"category": "feature"
}Response (201 Created):
{
"statusCode": 201,
"data": {
"_id": "507f1f77bcf86cd799439015",
"title": "Dark Mode Support",
"slug": "dark-mode-support",
"content": "Added dark mode toggle to the dashboard for better accessibility",
"version": "v2.1.0",
"category": "feature",
"status": "draft",
"workspaceId": "507f1f77bcf86cd799439012",
"createdBy": "507f1f77bcf86cd799439011",
"createdAt": "2026-03-01T10:40:00Z",
"updatedAt": "2026-03-01T10:40:00Z"
},
"message": "Release created successfully",
"success": true
}Note: Slug is auto-generated from the title using Slugify pre-save hook.
GET /releases?page=1&limit=10&status=draft&category=feature&search=dark
Authorization: Bearer {accessToken}Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
number | 1 | Page number (min: 1) |
limit |
number | 10 | Results per page (max: 50) |
status |
string | — | Filter: draft, published, archived |
category |
string | — | Filter: feature, improvement, bugfix, security, other |
search |
string | — | Search by title (case-insensitive) |
Response (200 OK):
{
"statusCode": 200,
"data": {
"releases": [
{
"_id": "507f1f77bcf86cd799439015",
"title": "Dark Mode Support",
"slug": "dark-mode-support",
"content": "Added dark mode toggle...",
"version": "v2.1.0",
"category": "feature",
"status": "draft",
"createdAt": "2026-03-01T10:40:00Z"
}
],
"pagination": {
"currentPage": 1,
"totalPages": 1,
"totalReleases": 1,
"limit": 10
}
},
"message": "Releases fetched successfully",
"success": true
}Security Notes:
- Search input is sanitized to prevent regex injection
- Invalid status/category values return
400 Bad Request - Limit is capped at 50 to prevent database overload
GET /releases/{releaseId}
Authorization: Bearer {accessToken}Response (200 OK):
{
"statusCode": 200,
"data": {
"_id": "507f1f77bcf86cd799439015",
"title": "Dark Mode Support",
"slug": "dark-mode-support",
"content": "Added dark mode toggle to the dashboard...",
"version": "v2.1.0",
"category": "feature",
"status": "draft",
"workspaceId": "507f1f77bcf86cd799439012",
"createdBy": "507f1f77bcf86cd799439011",
"createdAt": "2026-03-01T10:40:00Z",
"updatedAt": "2026-03-01T10:40:00Z"
},
"message": "Release fetched successfully",
"success": true
}PATCH /releases/{releaseId}
Authorization: Bearer {accessToken}
Content-Type: application/json
{
"title": "Dark Mode & Light Mode Support",
"content": "Updated content",
"version": "v2.1.1",
"category": "improvement"
}Response (200 OK):
{
"statusCode": 200,
"data": {
"_id": "507f1f77bcf86cd799439015",
"title": "Dark Mode & Light Mode Support",
"slug": "dark-mode-light-mode-support",
"version": "v2.1.1",
"category": "improvement"
},
"message": "Release updated successfully",
"success": true
}Note: If title changes, slug is automatically regenerated.
DELETE /releases/{releaseId}
Authorization: Bearer {accessToken}Response (200 OK):
{
"statusCode": 200,
"data": {},
"message": "Release deleted successfully",
"success": true
}PATCH /releases/{releaseId}/publish
Authorization: Bearer {accessToken}Response (200 OK):
{
"statusCode": 200,
"data": {
"status": "published",
"publishedAt": "2026-03-01T10:45:00Z"
},
"message": "Release published successfully",
"success": true
}GET /subscribers?page=1&limit=50&status=active&sortBy=subscribedAt&sortOrder=desc
Authorization: Bearer {accessToken}Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
number | 1 | Page number |
limit |
number | 50 | Results per page (max: 100) |
status |
string | active |
Filter: active, unsubscribed |
sortBy |
string | subscribedAt |
Sort field: subscribedAt, unsubscribedAt |
sortOrder |
string | desc |
Sort direction: asc, desc |
**Response (200 OK):**
```json
{
"statusCode": 200,
"data": {
"subscribers": [...],
"pagination": {
"currentPage": 1,
"totalPages": 1,
"total": 5,
"limit": 50
}
},
"message": "Subscribers fetched successfully",
"success": true
}
DELETE /subscribers/{subscriberId}
Authorization: Bearer {accessToken}Response (200 OK):
{
"statusCode": 200,
"data": {},
"message": "Subscriber deleted successfully",
"success": true
}-
User Registration
- Password is hashed using bcryptjs with salt rounds
- User document is created in MongoDB
- Automatic workspace creation for the user
-
Login
- User credentials are validated against stored hash
- JWT tokens are generated (access + refresh)
- Tokens are set in httpOnly cookies for XSS protection
-
Access Control
- Protected routes check Authorization header for Bearer token
- JWT middleware verifies token signature and expiry
- Invalid/expired tokens return 401 Unauthorized
-
Token Refresh
- Access token expires in 15 minutes (default)
- Refresh token allows obtaining new access tokens
- Refresh token rotation improves security
-
Logout
- Refresh token is invalidated in database
- Cookies are cleared on client
- Subsequent requests with old tokens will fail
Access Token Payload:
{
"_id": "507f1f77bcf86cd799439011",
"email": "user@example.com",
"iat": 1704110400,
"exp": 1704111300
}Refresh Token Payload:
{
"_id": "507f1f77bcf86cd799439011",
"iat": 1704110400,
"exp": 1704196800
}{
username: String, // Unique username
email: String, // Unique email
fullName: String, // User's full name
password: String, // Hashed password (select: false)
role: String, // 'owner' or 'contributor'
workspaceId: ObjectId, // Reference to workspace
avatar: String, // Optional profile picture URL
lastLoginAt: Date, // Last login timestamp
isActive: Boolean, // Account status
createdAt: Date, // Creation timestamp
updatedAt: Date // Last update timestamp
}{
name: String, // Workspace name
slug: String, // Unique subdomain slug
description: String, // Workspace description
logo: String, // Workspace logo URL
owner: ObjectId, // Owner user reference
members: [ObjectId], // Array of member user references
isActive: Boolean, // Workspace status
settings: {
theme: String, // 'light' or 'dark'
language: String, // Default language
timezone: String // Workspace timezone
},
createdAt: Date,
updatedAt: Date
}{
title: String, // Release title (required)
slug: String, // Auto-generated SEO slug
content: String, // Rich text content (required)
version: String, // Semantic version (optional)
category: String, // 'feature', 'improvement', 'bugfix', 'security', 'other'
status: String, // 'draft', 'published', 'archived'
workspaceId: ObjectId, // Reference to workspace
createdBy: ObjectId, // User who created the release
publishedAt: Date, // Publication timestamp
createdAt: Date, // Creation timestamp
updatedAt: Date // Last update timestamp
}{
email: String, // Subscriber email (unique per workspace)
workspaceId: ObjectId, // Reference to workspace
status: String, // 'active' or 'unsubscribed'
unsubscribeToken: String, // Unique token for one-click unsubscribe
subscribedAt: Date, // Timestamp of subscription
unsubscribedAt: Date, // Timestamp of unsubscription
createdAt: Date,
updatedAt: Date
}Create a .env file in the backend directory:
# Server Configuration
PORT=5000
NODE_ENV=development
# Database
MONGODB_URL=mongodb+srv://<username>:<password>@cluster.mongodb.net/<database_name>
# CORS
CORS_ORIGIN=http://localhost:5173
# JWT Secrets (use strong, random values in production)
ACCESS_TOKEN_SECRET=<your_access_token_secret>
ACCESS_TOKEN_EXPIRY=15m
REFRESH_TOKEN_SECRET=<your_refresh_token_secret>
REFRESH_TOKEN_EXPIRY=7d
# File Upload
CLOUDINARY_NAME=<your_cloudinary_name>
CLOUDINARY_API_KEY=<your_cloudinary_api_key>
CLOUDINARY_API_SECRET=<your_cloudinary_api_secret>
# Email (Optional)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=<your_email@example.com>
SMTP_PASS=<your_password>Development:
- Hot reload enabled
- Debug logging enabled
- CORS allows all origins (configure for security)
Production:
- Minified code
- Optimized database queries
- Strict CORS configuration
- Security headers enabled
Terminal 1 - Backend:
cd backend
npm run devTerminal 2 - Frontend:
cd frontend
npm run devThe project uses Prettier for code formatting and ESLint for linting.
Format code:
# Backend
cd backend && npx prettier --write .
# Frontend
cd frontend && npm run lintFiles are uploaded using Multer and stored in Cloudinary:
- User uploads file via POST request
- Multer validates and stores temporarily
- Cloudinary integration processes the file
- URL is returned and stored in database
Slugs are automatically generated from titles using the Slugify library:
// Title: "Dark Mode Support!"
// Generated Slug: "dark-mode-support"
// Handles:
// - Special characters
// - Accented characters (é, ñ, etc.)
// - Multiple spaces
// - Case conversionEach workspace is completely isolated:
- Users can only access releases in their workspace
- Each workspace has independent members and settings
- Subdomains can be configured per workspace
Database queries are optimized:
- Limit clamping prevents overload (max 50)
- Results are indexed by creation date
- Efficient filtering with MongoDB queries
- API Documentation: API Reference
- Authentication Guide: Authentication
- Database Schema: Database Models
- Environment Setup: Configuration
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
- Follow the existing code style
- Write meaningful commit messages
- Test your changes before submitting PR
- Update documentation as needed
This project is licensed under the ISC License - see the LICENSE file for details.
Amarnath Sharma
If you find this project helpful, please consider:
- Starring the repository ⭐
- Sharing it with others
- Reporting issues and bugs
- Contributing improvements
- Email notifications for subscribers
- Changelog RSS feed
- Public changelog pages
- Release scheduling
- Changelog analytics & metrics
- Webhook integrations (Slack, Discord, Teams)
- Changelog templates
- Multi-language support
- Dark mode for frontend
- Mobile app
Last Updated: March 1, 2026
© 2026 Amarnath Sharma. All Rights Reserved.