A robust, scalable RESTful API built with Node.js, Express, and MongoDB for managing personal expenses and tracking financial transactions. Features message queue integration with RabbitMQ, Redis caching, email notifications, and background workers for async processing.
- Runtime: Node.js
- Framework: Express.js
- Database: MongoDB (with Mongoose ODM)
- Message Queue: RabbitMQ (AMQP)
- Cache: Redis
- Authentication: JWT (JSON Web Tokens)
- Security: bcrypt for password hashing
- File Upload: Multer middleware
- Email Service: Nodemailer (Gmail)
- Containerization: Docker & Docker Compose
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │─────▶│ Express │─────▶│ MongoDB │
│ │ │ Server │ │ │
└─────────────┘ └──────┬──────┘ └─────────────┘
│
┌───────────┼───────────┐
│ │ │
┌──────▼─────┐ ┌──▼──────┐ ┌──▼──────────┐
│ RabbitMQ │ │ Redis │ │ Worker │
│ (Queue) │ │ (Cache) │ │ (Consumer) │
└────────────┘ └─────────┘ └─────────────┘
-
User Authentication & Authorization
- JWT-based session management
- Secure password hashing with bcrypt
- Session management with Redis
- Multi-device session support
-
Expense & Income Tracking
- Create, read, update, delete expenses/income
- Category-based expense organization
- Source-based income tracking
- File upload support for receipts
-
Dashboard Analytics
- Financial statistics and summaries
- Monthly trends (6-month history)
- Top expense categories
- Savings rate calculation
- Transaction statistics
- Redis caching for performance
-
Message Queue Integration
- Async event processing with RabbitMQ
- Event-driven architecture
- Separate worker process for consumers
- Retry mechanism with dead letter handling
-
Caching Layer
- Redis integration for high-performance caching
- Cache invalidation on data updates
- Fallback to database on cache miss
-
Email Notifications
- OTP generation and verification
- Email service with Nodemailer
- HTML email templates
-
Excel Export
- Export expense/income data to Excel format
-
Docker Support
- Multi-container setup with Docker Compose
- MongoDB, Redis, and RabbitMQ containers
- Persistent volumes for data
Before running this application, ensure you have:
- Node.js (v14 or higher)
- MongoDB (local or MongoDB Atlas)
- Redis (local or cloud)
- RabbitMQ (local or cloud)
- npm or yarn package manager
- Docker & Docker Compose (optional, for containerized setup)
git clone <repository-url>
cd backendnpm installCreate a .env file in the backend directory:
PORT=5000 NODE_ENV=development
DATABASE_URL=add your own db connection URL here
JWT_SECRET=your_own_jwt_secret_key_here
REDIS_URL=add your own REDIS connection URL here
RABBITMQ_USER=add your own RABBITMQ_USER name here RABBITMQ_PASS=add your pass here RABBITMQ_HOST=localhost RABBITMQ_PORT=add your port no here RABBITMQ_VHOST=add your vhost name here
EMAIL_USER=your-email@gmail.com EMAIL_PASS=your-app-password
Start all services using Docker Compose:
docker-compose up -dThis will start:
- MongoDB (port 27017)
- Redis (port 6379)
- RabbitMQ (AMQP: 5672, Management UI: 15672)
Access RabbitMQ Management UI at http://localhost:15672
- Username:
admin - Password:
admin123
Start MongoDB:
# Windows
net start MongoDB
# macOS (Homebrew)
brew services start mongodb-community
# Linux
sudo systemctl start mongodStart Redis:
# Windows (using Redis for Windows)
redis-server
# macOS (Homebrew)
brew services start redis
# Linux
sudo systemctl start redisStart RabbitMQ:
# Windows
rabbitmq-server
# macOS (Homebrew)
brew services start rabbitmq
# Linux
sudo systemctl start rabbitmq-servermkdir uploadsStart the main server:
npm run devStart the worker (in a separate terminal):
node workers/consumer.jsnpm startThe server will start on http://localhost:5000
backend/
├── config/ # Configuration files
│ ├── db.js # MongoDB connection
│ ├── redis.js # Redis client configuration
│ ├── rabbitmq.js # RabbitMQ connection manager (singleton)
│ └── queues.js # Queue definitions & routing keys
│
├── controllers/ # Request handlers
│ ├── authController.js # Authentication logic
│ ├── dashboardController.js # Dashboard endpoints
│ ├── expenseController.js # Expense CRUD operations
│ └── incomeController.js # Income CRUD operations
│
├── middleware/ # Express middleware
│ ├── authMiddleware.js # JWT authentication
│ └── uploadMiddleware.js # File upload handling
│
├── models/ # Mongoose schemas
│ ├── Expense.js # Expense model
│ ├── Income.js # Income model
│ └── User.js # User model
│
├── routes/ # API route definitions
│ ├── authRoutes.js # /api/auth/*
│ ├── dashboardRoutes.js # /api/dashboard/*
│ ├── expenseRoutes.js # /api/expenses/*
│ └── incomeRoutes.js # /api/income/*
│
├── services/ # Business logic layer
│ ├── dashboardService.js # Dashboard data aggregation & caching
│ ├── emailService.js # Email sending (Nodemailer)
│ ├── expenseService.js # Expense business logic & caching
│ ├── incomeService.js # Income business logic & caching
│ ├── messageConsumer.js # RabbitMQ message consumers
│ ├── messagePublisher.js # RabbitMQ message publishers
│ ├── otpService.js # OTP generation & verification
│ └── sessionService.js # Session management with Redis
│
├── workers/ # Background workers
│ └── consumer.js # Standalone RabbitMQ consumer process
│
├── uploads/ # Uploaded files (receipts/images)
│
├── .env # Environment variables
├── .gitignore # Git ignore rules
├── docker-compose.yml # Docker services configuration
├── package.json # Dependencies & scripts
├── README.md # Documentation
└── server.js # Application entry point
┌──────────────────────────────────────┐
│ USER │
├──────────────────────────────────────┤
│ PK _id: ObjectId │
│ fullName: String (required) │
│ email: String (unique, required) │
│ phone: String (nullable) │
│ password: String (hashed) │
│ profileImageUrl: String │
│ createdAt: Date │
│ updatedAt: Date │
└──────────────┬───────────────────────┘
│
│ 1
│
│ has many
│
┌──────────────┴──────────────┐
│ │
│ * │ *
│ │
┌────────────▼──────────────┐ ┌──────────▼──────────────┐
│ EXPENSE │ │ INCOME │
├───────────────────────────┤ ├─────────────────────────┤
│ PK _id: ObjectId │ │ PK _id: ObjectId │
│ FK userId: ObjectId │ │ FK userId: ObjectId │
│ icon: String │ │ icon: String │
│ category: String (*) │ │ source: String (*) │
│ amount: Number (*) │ │ amount: Number (*) │
│ date: Date │ │ date: Date │
│ description: String │ │ createdAt: Date │
│ createdAt: Date │ │ updatedAt: Date │
│ updatedAt: Date │ └─────────────────────────┘
└───────────────────────────┘
Indexes:
• userId (indexed)
• { userId: 1, date: -1 } (compound)
Legend: (*) = Required field
- User → Expense: One-to-Many (One user can have multiple expenses)
- User → Income: One-to-Many (One user can have multiple income records)
| Field | Type | Required | Unique | Default | Description |
|---|---|---|---|---|---|
| _id | ObjectId | Yes (auto) | Yes | - | Primary key |
| fullName | String | Yes | No | - | User's full name |
| String | Yes | Yes | - | User's email address | |
| phone | String | No | No | null | User's phone number |
| password | String | Yes | No | - | Hashed password (bcrypt, 10 rounds) |
| profileImageUrl | String | No | No | null | URL to user's profile image |
| createdAt | Date | Yes (auto) | No | Date.now | Account creation timestamp |
| updatedAt | Date | Yes (auto) | No | Date.now | Last update timestamp |
Schema Methods:
comparePassword(candidatePassword)- Compares plain text password with hashed password- Pre-save hook - Automatically hashes password before saving if modified
Indexes:
- email (unique index, auto-created)
| Field | Type | Required | Default | Validation | Description |
|---|---|---|---|---|---|
| _id | ObjectId | Yes (auto) | - | - | Primary key |
| userId | ObjectId | Yes | - | ref: 'User' | Foreign key to User |
| icon | String | No | 💰 | - | Emoji or icon representation |
| category | String | Yes | - | trimmed | Expense category (Food, Rent, etc.) |
| amount | Number | Yes | - | min: 0 | Expense amount (cannot be negative) |
| date | Date | No | Date.now | - | Date of expense |
| description | String | No | - | trimmed | Additional details about expense |
| createdAt | Date | Yes (auto) | Date.now | - | Record creation timestamp |
| updatedAt | Date | Yes (auto) | Date.now | - | Last update timestamp |
Indexes:
- userId (single field index)
- { userId: 1, date: -1 } (compound index for efficient queries)
Common Categories:
- Food & Dining
- Transportation
- Shopping
- Entertainment
- Bills & Utilities
- Healthcare
- Education
- Others
| Field | Type | Required | Default | Validation | Description |
|---|---|---|---|---|---|
| _id | ObjectId | Yes (auto) | - | - | Primary key |
| userId | ObjectId | Yes | - | ref: 'User' | Foreign key to User |
| icon | String | No | - | - | Emoji or icon representation |
| source | String | Yes | - | - | Income source (Salary, Freelance, etc.) |
| amount | Number | Yes | - | - | Income amount |
| date | Date | No | Date.now | - | Date of income |
| createdAt | Date | Yes (auto) | Date.now | - | Record creation timestamp |
| updatedAt | Date | Yes (auto) | Date.now | - | Last update timestamp |
Common Income Sources:
- Salary
- Freelance
- Business
- Investments
- Rental Income
- Others
Indexes are strategically placed for optimal query performance:
-
User Collection:
email(unique) - For authentication and user lookup
-
Expense Collection:
userId(single) - For filtering expenses by user{ userId: 1, date: -1 }(compound) - For sorted queries (most recent first)
-
Income Collection:
userId(implicit through ref) - For filtering income by user
- expense_exchange - Topic exchange for expense events
- income_exchange - Topic exchange for income events
- email_exchange - Topic exchange for email notifications
- analytics_exchange - Topic exchange for analytics events
- expense.created - New expense notifications
- expense.deleted - Expense deletion events
- income.created - New income notifications
- income.deleted - Income deletion events
- email.notification - Email sending queue
- expense.analytics - Expense analytics processing
- income.analytics - Income analytics processing
1. User creates expense
↓
2. expenseService saves to MongoDB
↓
3. messagePublisher publishes to expense_exchange
↓
4. Messages routed to:
- expense.created queue
- expense.analytics queue
↓
5. Worker consumes messages
↓
6. Cache updated, analytics processed
expenses:user:{userId}- User's expenses list (5 min TTL)income:user:{userId}- User's income list (5 min TTL)dashboard:user:{userId}- Dashboard data (5 min TTL)otp:{email}- OTP codes (10 min TTL)session:{userId}:{sessionId}- User sessions (1 hour TTL)user_sessions:{userId}- Active session IDs setstats:expense:{userId}- Expense statistics (24 hour TTL)stats:income:{userId}- Income statistics (24 hour TTL)
Cache is automatically invalidated when:
- Expense created/deleted
- Income created/deleted
- User logs out
- OTP verified
- Aggregates financial data from MongoDB
- Calculates statistics (savings rate, monthly trends)
- Implements Redis caching with 5-minute TTL
- Provides expense/income breakdown by category/source
- Returns recent transactions and analytics
- Sends OTP emails using Nodemailer
- Gmail SMTP integration
- HTML email templates
- Email delivery confirmation
- Business logic for expense operations
- Redis caching for expense lists
- Cache invalidation on create/delete
- RabbitMQ event publishing
- Excel export functionality
- Business logic for income operations
- Redis caching for income lists
- Cache invalidation on create/delete
- RabbitMQ event publishing
- Excel export functionality
- Publishes events to RabbitMQ exchanges
- Initializes exchanges and queues
- Event types: expense created/deleted, income created/deleted, email notifications
- Also publishes analytics events
- Consumes messages from RabbitMQ queues
- Updates Redis cache
- Processes analytics events
- Sends email notifications
- Retry mechanism (3 attempts)
- Generates 6-digit OTP codes
- Stores OTP in Redis with 10-minute expiry
- Verifies OTP and deletes on success
- TTL checking for OTP validity
- Creates user sessions in Redis
- Verifies active sessions
- Multi-device session support
- Session deletion (single or all devices)
- Tracks device info and last active time
The workers/consumer.js runs as a separate process to handle async message processing:
Features:
- Connects to RabbitMQ and Redis
- Starts all message consumers
- Graceful shutdown handling
- Error recovery and logging
- Can be scaled horizontally
Run worker:
node workers/consumer.js- Password Security: bcrypt hashing with salt rounds
- JWT Authentication: Token-based auth with expiration
- Session Management: Redis-based session store
- Protected Routes: Middleware authentication
- NoSQL Injection Prevention: Mongoose validation
- CORS: Configured for cross-origin requests
- Input Validation: Request data sanitization
- File Upload Security: Multer restrictions
- Environment Variables: Sensitive data in .env
| Variable | Description | Default | Required |
|---|---|---|---|
| PORT | Server port | 5000 | No |
| NODE_ENV | Environment | development | No |
| DATABASE_URL | MongoDB URI | - | Yes |
| JWT_SECRET | JWT signing key | - | Yes |
| REDIS_URL | Redis connection URL | redis://localhost:6379 | No |
| RABBITMQ_USER | RabbitMQ username | admin | No |
| RABBITMQ_PASS | RabbitMQ password | admin123 | No |
| RABBITMQ_HOST | RabbitMQ host | localhost | No |
| RABBITMQ_PORT | RabbitMQ port | 5672 | No |
| RABBITMQ_VHOST | RabbitMQ virtual host | expense_tracker | No |
| EMAIL_USER | Gmail address | - | Yes (for OTP) |
| EMAIL_PASS | Gmail app password | - | Yes (for OTP) |
The docker-compose.yml defines three services:
- Image:
mongo:latest - Port:
27017 - Volume: Persistent data storage
- Healthcheck: Connection validation
- Image:
redis:latest - Port:
6379 - Volume: Persistent data storage
- Healthcheck: PING response
- Image:
rabbitmq:3.12-management-alpine - Ports:
5672(AMQP),15672(Management UI) - Volumes: Data and logs persistence
- Default credentials: admin/admin123
- Healthcheck: RabbitMQ diagnostics
Access at http://localhost:15672
- Monitor queues and exchanges
- View message rates
- Check consumer connections
docker exec -it expense_tracker_redis redis-cli
# Check keys
KEYS *
# Get specific key
GET dashboard:user:123Connect to mongodb://localhost:27017
- View collections
- Query data
- Monitor performance
- Redis Caching: 5-minute TTL for frequently accessed data
- Database Indexing: User IDs indexed for fast queries
- Aggregation Pipelines: Efficient MongoDB queries
- Message Queue: Async processing offloaded to workers
- Connection Pooling: MongoDB and Redis connection reuse
- Graceful Degradation: Falls back to database if cache fails
- Graceful Fallbacks: Database fallback when Redis fails
- Retry Mechanism: RabbitMQ consumers retry 3 times
- Error Logging: Comprehensive console logging
- Validation: Input validation at controller level
- HTTP Status Codes: Proper status codes for errors
# Check if MongoDB is running
docker ps | grep mongodb
# Or on local machine
mongosh --eval "db.adminCommand('ping')"# Check Redis connection
docker exec -it expense_tracker_redis redis-cli ping
# Should return PONG# Check RabbitMQ status
docker exec -it expense_tracker_rabbitmq rabbitmq-diagnostics ping
# Access management UI
open http://localhost:15672- Ensure RabbitMQ is running
- Check worker logs for errors
- Verify queue bindings in RabbitMQ UI
- Restart worker process
- Verify Redis is connected
- Check Redis logs for errors
- Ensure cache keys are correctly formatted
| Issue | Solution |
|---|---|
| Port already in use | Change PORT in .env |
| JWT errors | Verify JWT_SECRET is set |
| Module not found | Run npm install |
| Authentication errors | Check JWT token in Authorization header |
| Email not sending | Verify Gmail app password |
| Messages not consumed | Start worker with node workers/consumer.js |
| Redis connection failed | Check REDIS_URL in .env |
- Fork the repository
- Create feature branch (
git checkout -b feature/AmazingFeature) - Commit changes (
git commit -m 'Add AmazingFeature') - Push to branch (
git push origin feature/AmazingFeature) - Open Pull Request
This project is licensed under the MIT License.
For questions or support, please open an issue in the repository.
- Express.js community
- MongoDB documentation
- RabbitMQ tutorials
- Redis documentation
- Nodemailer documentation
- Docker community
- Node.js ecosystem
I'll see you in the next one! 🚀