A Node.js/Express REST API for managing and tracking subscriptions with automated reminder workflows. The API helps users monitor their subscription services, track renewal dates, and receive timely email notifications.
- User authentication with JWT tokens
- Subscription management (create, read, update, delete operations)
- Automatic email reminders before subscription renewals
- Subscription categorization (entertainment, utilities, software, education, health, other)
- Multiple currency support (USD, EUR, GBP, JPY, AUD, CAD, CHF, CNY, SEK, NZD)
- Flexible billing frequency (monthly, yearly)
- Subscription status tracking (active, canceled, expired)
- Workflow automation using Upstash QStash
- Security features with Arcjet rate limiting
- MongoDB database for data persistence
Before you begin, ensure you have the following installed:
- Node.js (v16 or higher)
- npm (Node Package Manager)
- MongoDB account (for database)
- Upstash account (for workflow management)
- Arcjet account (for security/rate limiting)
- SMTP email service (for sending reminders)
- Clone the repository:
git clone <repository-url>
cd subscription-tracker- Install dependencies:
npm install- Create a
.env.development.localfile in the project root directory:
PORT=5500
SERVER_URL=http://localhost:5500
DB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/?appName=<app-name>
JWT_SECRET=your_jwt_secret_key
JWT_EXPIRES_IN=1d
ARCJET_KEY=your_arcjet_key
ARCJET_ENV=development
QSTASH_URL=http://127.0.0.1:8080
QSTASH_TOKEN=your_qstash_token
QSTASH_CURRENT_SIGNING_KEY=your_current_signing_key
QSTASH_NEXT_SIGNING_KEY=your_next_signing_key
EMAIL_PASSWORD=your_email_password
- For production environment, create a
.env.production.localfile with appropriate values:
PORT=your_production_port
SERVER_URL=your_production_url
DB_URI=your_production_mongodb_uri
JWT_SECRET=your_production_jwt_secret
JWT_EXPIRES_IN=1d
ARCJET_KEY=your_production_arcjet_key
ARCJET_ENV=production
QSTASH_URL=your_production_qstash_url
QSTASH_TOKEN=your_production_qstash_token
QSTASH_CURRENT_SIGNING_KEY=your_production_signing_key
QSTASH_NEXT_SIGNING_KEY=your_production_next_signing_key
EMAIL_PASSWORD=your_production_email_password
Run the application with nodemon for automatic restart on file changes:
npm run devThe API will be available at http://localhost:5500
npm start- Endpoint:
POST /api/auth/sign-up - Description: Register a new user
- Request Body:
{ "name": "John Doe", "email": "john@example.com", "password": "securePassword123" } - Response:
{ "success": true, "message": "User registered successfully", "data": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "user": { "_id": "507f1f77bcf86cd799439011", "name": "John Doe", "email": "john@example.com", "createdAt": "2024-04-22T10:30:00Z" } } }
- Endpoint:
POST /api/auth/sign-in - Description: Authenticate user and get JWT token
- Request Body:
{ "email": "john@example.com", "password": "securePassword123" } - Response:
{ "success": true, "message": "User signed in successfully", "data": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "user": { "_id": "507f1f77bcf86cd799439011", "name": "John Doe", "email": "john@example.com" } } }
- Endpoint:
POST /api/auth/sign-out - Description: Sign out user (implementation in progress)
- Authentication: Required
- Response: Status 200 OK
- Endpoint:
GET /api/users - Description: Retrieve all users (public endpoint)
- Response:
{ "success": true, "data": [ { "_id": "507f1f77bcf86cd799439011", "name": "John Doe", "email": "john@example.com", "createdAt": "2024-04-22T10:30:00Z" } ] }
- Endpoint:
GET /api/users/:id - Description: Retrieve a specific user by ID
- Authentication: Required (JWT token)
- Path Parameters:
id(string): The user's MongoDB ObjectId
- Response:
{ "success": true, "data": { "_id": "507f1f77bcf86cd799439011", "name": "John Doe", "email": "john@example.com", "createdAt": "2024-04-22T10:30:00Z" } }
- Endpoint:
POST /api/users - Description: Create a new user (placeholder endpoint)
- Response: Status 200 with placeholder response
- Endpoint:
PUT /api/users/:id - Description: Update user information (placeholder endpoint)
- Path Parameters:
id(string): The user's MongoDB ObjectId
- Response: Status 200 with placeholder response
- Endpoint:
DELETE /api/users/:id - Description: Delete a user (placeholder endpoint)
- Path Parameters:
id(string): The user's MongoDB ObjectId
- Response: Status 200 with placeholder response
- Endpoint:
GET /api/subscriptions - Description: Get all subscriptions (placeholder endpoint)
- Response: Status 200 with placeholder response
- Endpoint:
GET /api/subscriptions/:id - Description: Get a specific subscription (placeholder endpoint)
- Path Parameters:
id(string): The subscription's MongoDB ObjectId
- Response: Status 200 with placeholder response
- Endpoint:
POST /api/subscriptions - Description: Create a new subscription with automated reminder workflow
- Authentication: Required (JWT token)
- Request Body:
{ "name": "Netflix", "price": 15.99, "currency": "USD", "frequency": "monthly", "category": "entertainment", "paymentMethod": "Credit Card", "status": "active", "startDate": "2024-04-01T00:00:00Z", "renewalDate": "2024-05-01T00:00:00Z" } - Response:
{ "success": true, "data": { "_id": "507f1f77bcf86cd799439012", "name": "Netflix", "price": 15.99, "currency": "USD", "frequency": "monthly", "category": "entertainment", "paymentMethod": "Credit Card", "status": "active", "startDate": "2024-04-01T00:00:00Z", "renewalDate": "2024-05-01T00:00:00Z", "user": "507f1f77bcf86cd799439011", "createdAt": "2024-04-22T10:30:00Z" }, "workflowRunId": "workflow_run_id_123" }
- Endpoint:
GET /api/subscriptions/user/:id - Description: Get all subscriptions for a specific user
- Authentication: Required (JWT token)
- Path Parameters:
id(string): The user's MongoDB ObjectId
- Response:
{ "success": true, "data": [ { "_id": "507f1f77bcf86cd799439012", "name": "Netflix", "price": 15.99, "currency": "USD", "frequency": "monthly", "category": "entertainment", "paymentMethod": "Credit Card", "status": "active", "startDate": "2024-04-01T00:00:00Z", "renewalDate": "2024-05-01T00:00:00Z", "user": "507f1f77bcf86cd799439011" } ] }
- Endpoint:
PUT /api/subscriptions/:id - Description: Update subscription information (placeholder endpoint)
- Authentication: Required (JWT token)
- Path Parameters:
id(string): The subscription's MongoDB ObjectId
- Response: Status 200 with placeholder response
- Endpoint:
DELETE /api/subscriptions/:id - Description: Delete a subscription (placeholder endpoint)
- Path Parameters:
id(string): The subscription's MongoDB ObjectId
- Response: Status 200 with placeholder response
- Endpoint:
PUT /api/subscriptions/:id/cancel - Description: Cancel an active subscription (placeholder endpoint)
- Path Parameters:
id(string): The subscription's MongoDB ObjectId
- Response: Status 200 with placeholder response
- Endpoint:
GET /api/subscriptions/upcoming-renewals - Description: Get subscriptions with upcoming renewal dates (placeholder endpoint)
- Response: Status 200 with placeholder response
- Endpoint:
POST /api/workflow/subscriptions/reminder - Description: Trigger automated reminder workflow for subscriptions
- Request Body:
{ "subscriptionId": "507f1f77bcf86cd799439012" } - Response: Status 200 OK
- Notes: Automatically triggered when a subscription is created. Sends email reminders 7, 5, 2, and 1 day(s) before renewal.
{
_id: ObjectId (primary key)
name: String (required, 2-50 characters)
email: String (required, unique, lowercase)
password: String (required, hashed with bcryptjs, min 6 characters)
createdAt: Date (auto-generated)
updatedAt: Date (auto-updated)
}
{
_id: ObjectId (primary key)
name: String (required, 2-100 characters)
price: Number (required, positive)
currency: String (enum: USD, EUR, GBP, JPY, AUD, CAD, CHF, CNY, SEK, NZD, default: USD)
frequency: String (enum: monthly, yearly)
category: String (enum: entertainment, utilities, software, education, health, other)
paymentMethod: String (required)
status: String (enum: active, canceled, expired, default: active)
startDate: Date (required, cannot be in future)
renewalDate: Date (calculated from startDate and frequency)
user: ObjectId (reference to User, required, indexed)
createdAt: Date (auto-generated)
updatedAt: Date (auto-updated)
}
The API uses JWT (JSON Web Tokens) for authentication:
- When a user signs up or signs in, they receive a JWT token in the response
- To access protected endpoints, include the token in the Authorization header:
Authorization: Bearer <your_jwt_token>
- The token expires based on the
JWT_EXPIRES_INenvironment variable (default: 1 day)
The following endpoints require authentication:
GET /api/users/:idPOST /api/subscriptionsGET /api/subscriptions/user/:idPUT /api/subscriptions/:id
The API returns standardized error responses with appropriate HTTP status codes:
- 400: Bad Request (validation error, missing required fields)
- 401: Unauthorized (invalid credentials, expired token)
- 403: Forbidden (insufficient permissions)
- 404: Not Found (resource does not exist)
- 409: Conflict (duplicate email, user already exists)
- 500: Internal Server Error
Error Response Format:
{
"success": false,
"message": "Error description"
}- Node.js: JavaScript runtime
- Express.js: Web framework
- MongoDB: NoSQL database
- Mongoose: MongoDB object modeling
- JWT: JSON Web Token for authentication
- bcryptjs: Password hashing
- Upstash QStash: Workflow automation for reminders
- Arcjet: Rate limiting and security
- Nodemailer: Email sending
- Day.js: Date manipulation
- Morgan: HTTP request logging
- Cookie Parser: Cookie parsing middleware
- ESLint: Code linting
- Nodemon: Development server with auto-reload
The project uses ESLint for code quality. Configuration is in eslint.config.js.
subscription-tracker/
├── config/ # Configuration files
│ ├── env.js # Environment variables
│ ├── arcjet.js # Arcjet security configuration
│ ├── upstash.js # Upstash workflow configuration
│ └── nodemailer.js # Email configuration
├── controllers/ # Business logic
│ ├── auth.controller.js
│ ├── user.controller.js
│ ├── subscription.controller.js
│ └── workflow.controller.js
├── middlewares/ # Express middleware
│ ├── auth.middleware.js
│ ├── error.middleware.js
│ └── arcjet.middleware.js
├── models/ # MongoDB schemas
│ ├── user.model.js
│ ├── subscription.model.js
│ └── auth.model.js
├── routes/ # API routes
│ ├── auth.routes.js
│ ├── user.routes.js
│ ├── subscription.routes.js
│ └── workflow.routes.js
├── utils/ # Utility functions
│ └── send-email.js
├── database/ # Database connection
│ └── mongodb.js
├── app.js # Express app setup
├── package.json # Dependencies and scripts
└── README.md # This file
You can test the API using tools like:
- Postman: Visual API testing platform
- cURL: Command-line tool for making HTTP requests
- Thunder Client: VS Code extension
- REST Client: VS Code extension
Example cURL request:
# Sign up
curl -X POST http://localhost:5500/api/auth/sign-up \
-H "Content-Type: application/json" \
-d '{"name":"John Doe","email":"john@example.com","password":"securePassword123"}'
# Sign in
curl -X POST http://localhost:5500/api/auth/sign-in \
-H "Content-Type: application/json" \
-d '{"email":"john@example.com","password":"securePassword123"}'
# Create subscription (requires token)
curl -X POST http://localhost:5500/api/subscriptions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your_token>" \
-d '{"name":"Netflix","price":15.99,"currency":"USD","frequency":"monthly","category":"entertainment","paymentMethod":"Credit Card","startDate":"2024-04-01T00:00:00Z"}'- Passwords are hashed using bcryptjs before storage
- JWT tokens are used for session management
- Arcjet middleware provides rate limiting and DDoS protection
- Sensitive fields (passwords) are excluded from responses where appropriate
- Email addresses are stored in lowercase for consistency
- Input validation is performed on all user inputs
- Ensure MongoDB URI in environment file is correct
- Verify network access is allowed in MongoDB Atlas
- Check database credentials
- The token expires after 1 day by default
- Request a new token by signing in again
- Update JWT_EXPIRES_IN in environment file if needed
- Verify email configuration in environment file
- Check Upstash QStash configuration
- Ensure subscription renewal date is in the future
- Check email service logs for delivery issues
- Requests are rate-limited by Arcjet
- Wait and retry after rate limit is exceeded
- Contact Arcjet support to increase limits if needed
When contributing to this project:
- Follow the existing code structure
- Use the established naming conventions
- Test your changes before committing
- Write clear commit messages
- Update documentation as needed
For issues, questions, or feature requests, please refer to the project repository or contact the development team.
Current Version: 0.0.1