Learn how to build an expense tracker with Lovable using our step-by-step guide. Streamline budgeting, track spending, and boost your financial insights!
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
Project Setup and File Organization
index.html
, style.css
, and expense.js
.
Creating the HTML Structure
index.html
.index.html
; this creates the user interface with a form to add expenses and a list to display them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Expense Tracker</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Expense Tracker</h1>
<form id="expense-form">
<input type="text" id="description" placeholder="Expense Description">
<input type="number" id="amount" placeholder="Amount">
<button type="submit">Add Expense</button>
</form>
<ul id="expense-list"></ul>
<script src="expense.js"></script>
</body>
</html>
Adding CSS Styling
style.css
in your Lovable project.
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f4f4f4;
}
h1 {
text-align: center;
}
form {
max-width: 400px;
margin: 0 auto 20px;
display: flex;
gap: 10px;
}
input[type="text"],
input[type="number"] {
flex: 1;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 8px 12px;
border: none;
border-radius: 4px;
background-color: #5cb85c;
color: #fff;
cursor: pointer;
}
ul#expense-list {
max-width: 400px;
margin: 0 auto;
list-style-type: none;
padding: 0;
}
ul#expense-list li {
background-color: #fff;
margin-bottom: 8px;
padding: 10px;
border-radius: 4px;
border: 1px solid #ddd;
}
Implementing the Expense Tracking Logic
expense.js
in your Lovable project.expense.js
. This script handles form submissions, stores expense entries in localStorage
, and displays them on page load.expense.js
and referenced in your index.html
as shown earlier.
document.getElementById('expense-form').addEventListener('submit', function(e) {
e.preventDefault();
var description = document.getElementById('description').value;
var amount = document.getElementById('amount').value;
if(description && amount) {
// Create a new expense list item
var expenseItem = document.createElement('li');
expenseItem.textContent = description + " - $" + amount;
document.getElementById('expense-list').appendChild(expenseItem);
// Save to localStorage
var expenses = JSON.parse(localStorage.getItem('expenses')) || [];
expenses.push({description: description, amount: amount});
localStorage.setItem('expenses', JSON.stringify(expenses));
// Clear the form inputs
document.getElementById('description').value = '';
document.getElementById('amount').value = '';
}
});
// Load saved expenses when the page loads
window.addEventListener('load', function() {
var expenses = JSON.parse(localStorage.getItem('expenses')) || [];
var expenseList = document.getElementById('expense-list');
expenses.forEach(function(exp) {
var expenseItem = document.createElement('li');
expenseItem.textContent = exp.description + " - $" + exp.amount;
expenseList.appendChild(expenseItem);
});
});
Finalizing and Testing Your Expense Tracker
index.html
, style.css
, and expense.js
) to ensure the code is correctly inserted.index.html
and interact with your expense tracker by adding expense entries.
Adding Dependency Comments (If Needed)
<head>
section of index.html
.
<script src="https://cdn.example.com/library.js"></script>
Review
localStorage
ensures that added records persist across page reloads.
const express = require('express');
const mongoose = require('mongoose');
const app = express();
app.use(express.json());
mongoose.connect('mongodb://localhost:27017/lovable-expenses', { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('MongoDB connected'))
.catch(err => console.error(err));
const expenseSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'User' },
amount: { type: Number, required: true },
category: { type: String, required: true },
date: { type: Date, default: Date.now },
description: String,
paymentMethod: String
});
const Expense = mongoose.model('Expense', expenseSchema);
app.post('/api/expenses', async (req, res) => {
try {
const expense = new Expense(req.body);
await expense.save();
res.status(201).json(expense);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.get('/api/expenses', async (req, res) => {
try {
const { userId, category } = req.query;
const query = {};
if (userId) query.userId = userId;
if (category) query.category = category;
const expenses = await Expense.find(query).sort({ date: -1 });
res.json(expenses);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.get('/api/expenses/summary', async (req, res) => {
try {
const { userId, startDate, endDate } = req.query;
const match = { userId: mongoose.Types.ObjectId(userId) };
if (startDate || endDate) {
match.date = {};
if (startDate) match.date.$gte = new Date(startDate);
if (endDate) match.date.$lte = new Date(endDate);
}
const summary = await Expense.aggregate([
{ $match: match },
{ $group: { \_id: "$category", totalAmount: { $sum: "$amount" } } }
]);
res.json(summary);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
const express = require('express');
const axios = require('axios');
const router = express.Router();
router.get('/api/expenses/convert', async (req, res) => {
try {
const { amount, fromCurrency, toCurrency } = req.query;
if (!amount || !fromCurrency || !toCurrency) {
return res.status(400).json({ error: 'Missing required query parameters: amount, fromCurrency, toCurrency' });
}
const apiKey = process.env.CURRENCY_API_KEY;
const apiUrl = `https://api.exchangerate-api.com/v4/latest/${fromCurrency}`;
const response = await axios.get(apiUrl);
if (!response.data || !response.data.rates) {
return res.status(500).json({ error: 'Failed to retrieve conversion rates.' });
}
const conversionRate = response.data.rates[toCurrency];
if (!conversionRate) {
return res.status(400).json({ error: 'Target currency not supported.' });
}
const convertedAmount = parseFloat(amount) \* conversionRate;
res.json({
originalAmount: parseFloat(amount),
fromCurrency,
toCurrency,
conversionRate,
convertedAmount
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;
const express = require('express');
const mongoose = require('mongoose');
const nodemailer = require('nodemailer');
const router = express.Router();
const expenseSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
amount: { type: Number, required: true },
category: { type: String, required: true },
date: { type: Date, default: Date.now },
description: String
});
const Expense = mongoose.model('Expense', expenseSchema);
const userSchema = new mongoose.Schema({
email: { type: String, required: true },
monthlyBudget: { type: Number, required: true }
});
const User = mongoose.model('User', userSchema);
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL\_USER,
pass: process.env.EMAIL\_PASS
}
});
router.put('/api/expenses/:id', async (req, res) => {
try {
const expenseId = req.params.id;
const updatedData = req.body;
const expense = await Expense.findByIdAndUpdate(expenseId, updatedData, { new: true });
if (!expense) return res.status(404).json({ error: 'Expense not found' });
const startOfMonth = new Date(expense.date.getFullYear(), expense.date.getMonth(), 1);
const endOfMonth = new Date(expense.date.getFullYear(), expense.date.getMonth() + 1, 0);
const monthlyExpenses = await Expense.aggregate([
{ $match: { userId: expense.userId, date: { $gte: startOfMonth, $lte: endOfMonth } } },
{ $group: { \_id: null, total: { $sum: "$amount" } } }
]);
const user = await User.findById(expense.userId);
if (user && monthlyExpenses.length > 0 && monthlyExpenses[0].total > user.monthlyBudget) {
await transporter.sendMail({
from: process.env.EMAIL\_USER,
to: user.email,
subject: 'Alert: Monthly Budget Exceeded',
text: `You have exceeded your monthly budget of ${user.monthlyBudget}. Your current total is ${monthlyExpenses[0].total}.`
});
}
res.json(expense);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
Understanding the Project Scope and Objectives
Researching AI Code Generators
Setting Up Your Development Environment
Designing the Application Architecture
Integrating AI for Automatic Code Generation
def add_expense(expenses, new_entry):
"""
Adds a new expense entry to the list.
:param expenses: Current list of expenses.
:param new\_entry: Dictionary with expense details.
:return: Updated list of expenses.
"""
expenses.append(new\_entry)
return expenses
Building the User Interface
Implementing Core Functionalities
Testing and Quality Assurance
Security Best Practices
Documentation and Version Control
Deployment and Maintenance
Iterating and Learning
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.