Managing finances can often feel like navigating a complex maze. Keeping track of income and expenses, budgeting, and understanding where your money goes can be a daunting task. However, what if you could simplify this process with a custom-built, interactive expense tracker right in your web browser? This tutorial will guide you, step-by-step, in building a simple, yet functional, expense tracker using Node.js, Express, and a touch of JavaScript for the front-end. This project is perfect for beginners and intermediate developers looking to expand their skills and gain practical experience with web development concepts.
Why Build an Expense Tracker?
Beyond the personal benefits of financial organization, building an expense tracker offers several learning opportunities. It allows you to:
- Practice Full-Stack Development: You’ll work on both the front-end (user interface) and back-end (server-side logic), integrating them to create a complete application.
- Learn Database Interaction: You’ll learn how to store and retrieve data, a fundamental skill in almost any web application.
- Understand API Design: You’ll create APIs (Application Programming Interfaces) to handle data requests and responses between the front-end and back-end.
- Improve JavaScript Skills: You’ll enhance your JavaScript skills by implementing user interactions and data manipulation on the front-end.
Prerequisites
Before we begin, ensure you have the following installed on your system:
- Node.js and npm (Node Package Manager): These are essential for running JavaScript on the server and managing project dependencies. You can download them from nodejs.org.
- A Code Editor: Such as Visual Studio Code, Sublime Text, or Atom.
- Basic HTML, CSS, and JavaScript knowledge: While we’ll cover the basics, a familiarity with these languages will be helpful.
Setting Up the Project
Let’s start by creating a new project directory and initializing our Node.js project. Open your terminal or command prompt and run the following commands:
mkdir expense-tracker
cd expense-tracker
npm init -y
This creates a directory called expense-tracker, navigates into it, and initializes a package.json file with default settings. The package.json file will manage our project’s dependencies.
Installing Dependencies
We’ll be using the following packages:
- Express: A popular Node.js web application framework, simplifying server creation and routing.
- body-parser: Middleware for parsing incoming request bodies (e.g., JSON).
- sqlite3: A lightweight, file-based database.
- cors: Middleware to enable Cross-Origin Resource Sharing (CORS), allowing our front-end (running on a different port) to communicate with our back-end.
Install these dependencies using npm:
npm install express body-parser sqlite3 cors
Creating the Server (Back-end)
Create a file named server.js in your project directory. This file will contain our server-side code. Let’s start by importing the necessary modules and setting up our Express application:
const express = require('express');
const bodyParser = require('body-parser');
const sqlite3 = require('sqlite3').verbose();
const cors = require('cors');
const app = express();
const port = 3001; // Or any available port
app.use(cors()); // Enable CORS for all routes
app.use(bodyParser.json()); // Parse JSON request bodies
Explanation:
- We import the required modules.
- We create an Express application instance (
app). - We define the port the server will listen on.
- We use the
cors()middleware to enable CORS. This is crucial for allowing our front-end, which will likely run on a different port (e.g., 3000), to make requests to our back-end. - We use
bodyParser.json()middleware to parse incoming request bodies that are in JSON format.
Setting Up the Database
Next, let’s set up our SQLite database. We’ll create a database file (e.g., expenses.db) and a table to store our expense data. Add the following code below the middleware setup in server.js:
const db = new sqlite3.Database('expenses.db', (err) => {
if (err) {
console.error('Database connection error:', err.message);
}
console.log('Connected to the SQLite database.');
db.run(
`CREATE TABLE IF NOT EXISTS expenses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
description TEXT,
amount REAL,
date TEXT
)`,
(err) => {
if (err) {
console.error('Table creation error:', err.message);
}
console.log('Expenses table created or already exists.');
}
);
});
Explanation:
- We create a new SQLite database instance, connecting to a file named
expenses.db. - We handle potential connection errors.
- We create an
expensestable if it doesn’t already exist. The table has the following columns: id: An auto-incrementing primary key.description: A text field for the expense description.amount: A numeric field for the expense amount.date: A text field for the expense date.
Creating API Endpoints
Now, let’s create the API endpoints that our front-end will use to interact with the back-end. We’ll need endpoints for:
- Getting all expenses: Retrieve a list of all expenses from the database.
- Adding a new expense: Add a new expense to the database.
Add the following code below the database setup in server.js:
// Get all expenses
app.get('/expenses', (req, res) => {
db.all('SELECT * FROM expenses', (err, rows) => {
if (err) {
console.error('Error fetching expenses:', err.message);
res.status(500).json({ error: 'Failed to retrieve expenses' });
return;
}
res.json(rows);
});
});
// Add a new expense
app.post('/expenses', (req, res) => {
const { description, amount, date } = req.body;
if (!description || !amount || !date) {
res.status(400).json({ error: 'Description, amount, and date are required' });
return;
}
db.run(
'INSERT INTO expenses (description, amount, date) VALUES (?, ?, ?)',
[description, amount, date],
function (err) {
if (err) {
console.error('Error adding expense:', err.message);
res.status(500).json({ error: 'Failed to add expense' });
return;
}
res.status(201).json({ id: this.lastID, description, amount, date }); // Send back the newly created expense
}
);
});
Explanation:
GET /expenses: This endpoint retrieves all expenses from the database.- It uses the
db.all()method to execute a SQL query (SELECT * FROM expenses) and fetches all rows. - If an error occurs, it logs the error and sends a 500 (Internal Server Error) response with an error message.
- If successful, it sends a 200 (OK) response with the expense data in JSON format.
POST /expenses: This endpoint adds a new expense to the database.- It extracts the
description,amount, anddatefrom the request body (req.body). - It validates that all required fields are present. If not, it sends a 400 (Bad Request) response.
- It uses the
db.run()method to execute anINSERTSQL query, inserting the expense data into theexpensestable. - If an error occurs, it logs the error and sends a 500 (Internal Server Error) response.
- If successful, it sends a 201 (Created) response with the ID of the newly created expense, along with the expense details.
Starting the Server
Finally, add the following code to start the server and listen for incoming requests:
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
This code starts the Express server and makes it listen on the specified port. Open your terminal, navigate to your project directory, and run the server using the command: node server.js. You should see the message “Server listening on port 3001” (or whatever port you specified) in your console.
Building the Front-End (Basic HTML, CSS, and JavaScript)
Now, let’s create a simple front-end using HTML, CSS, and JavaScript. Create an index.html file in your project directory. This is a basic HTML structure:
<title>Expense Tracker</title>
<div class="container">
<h1>Expense Tracker</h1>
<div class="input-section">
<label for="description">Description:</label>
<label for="amount">Amount:</label>
<label for="date">Date:</label>
<button id="add-expense">Add Expense</button>
</div>
<h2>Expenses</h2>
<ul id="expense-list"></ul>
</div>
Explanation:
- We have a basic HTML structure with a title and a container div.
- We include a link to
style.cssfor styling. - We include a link to
script.jsfor our JavaScript code. - The
input-sectioncontains input fields for description, amount, and date, along with an “Add Expense” button. - The
expense-listis an unordered list where we’ll display the expenses.
Create a style.css file in the same directory and add some basic styling:
body {
font-family: sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 80%;
max-width: 600px;
}
h1 {
text-align: center;
color: #333;
}
.input-section {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"], input[type="number"], input[type="date"] {
width: 95%;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
width: 100%;
}
button:hover {
background-color: #3e8e41;
}
#expense-list {
list-style: none;
padding: 0;
}
#expense-list li {
padding: 10px;
border-bottom: 1px solid #eee;
}
#expense-list li:last-child {
border-bottom: none;
}
Finally, create a script.js file in the same directory. This file will contain the JavaScript code to handle user interactions and communicate with the back-end:
const descriptionInput = document.getElementById('description');
const amountInput = document.getElementById('amount');
const dateInput = document.getElementById('date');
const addExpenseButton = document.getElementById('add-expense');
const expenseList = document.getElementById('expense-list');
// Function to fetch and display expenses
async function getExpenses() {
try {
const response = await fetch('http://localhost:3001/expenses'); // Replace with your server URL
const expenses = await response.json();
displayExpenses(expenses);
} catch (error) {
console.error('Error fetching expenses:', error);
}
}
// Function to add an expense
async function addExpense(description, amount, date) {
try {
const response = await fetch('http://localhost:3001/expenses', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ description, amount, date })
});
if (!response.ok) {
throw new Error('Failed to add expense');
}
const newExpense = await response.json();
displayExpense(newExpense);
// Clear input fields
descriptionInput.value = '';
amountInput.value = '';
dateInput.value = '';
} catch (error) {
console.error('Error adding expense:', error);
}
}
// Function to display an expense
function displayExpense(expense) {
const listItem = document.createElement('li');
listItem.textContent = `${expense.description} - $${expense.amount} - ${expense.date}`;
expenseList.appendChild(listItem);
}
// Function to display multiple expenses
function displayExpenses(expenses) {
expenseList.innerHTML = ''; // Clear existing list
expenses.forEach(expense => {
displayExpense(expense);
});
}
// Event listener for the "Add Expense" button
addExpenseButton.addEventListener('click', () => {
const description = descriptionInput.value;
const amount = parseFloat(amountInput.value);
const date = dateInput.value;
if (description && !isNaN(amount) && date) {
addExpense(description, amount, date);
}
});
// Initial load: Fetch and display expenses when the page loads
getExpenses();
Explanation:
- We get references to the input fields, the “Add Expense” button, and the expense list.
getExpenses(): This function fetches expenses from the back-end using the/expensesendpoint.- It uses the
fetch()API to make a GET request to the server. - It parses the response as JSON and calls
displayExpenses()to display the expenses. - Error handling is included.
addExpense(description, amount, date): This function adds a new expense to the back-end using the/expensesendpoint.- It uses the
fetch()API to make a POST request to the server, sending the expense data in JSON format. - It handles the response and calls
displayExpense()to display the newly added expense. - It clears the input fields after successfully adding an expense.
- Error handling is included.
displayExpense(expense): This function creates a list item (<li>) to display a single expense.displayExpenses(expenses): This function clears the expense list and iterates over the expenses to display each one.- An event listener is attached to the “Add Expense” button.
- When the button is clicked, it retrieves the input values, validates them, and calls
addExpense()if the input is valid. getExpenses()is called when the page loads to display existing expenses.
Running the Application
1. **Start the Server:** Open your terminal, navigate to your project directory (where server.js is located), and run: node server.js.
2. **Open the Front-End:** Open index.html in your web browser. You can typically do this by double-clicking the file or by opening it through your code editor’s live server feature.
You should now see the expense tracker interface. Enter expense details, click “Add Expense,” and the expenses should appear in the list. If you refresh the page, the expenses you’ve added should persist, thanks to the database on the back-end.
Common Mistakes and Troubleshooting
Here are some common mistakes and how to fix them:
- CORS Errors: If you get errors related to CORS (e.g., “No ‘Access-Control-Allow-Origin’ header is present”), make sure you have the
corsmiddleware enabled in yourserver.jsfile (app.use(cors())). Also, ensure that the front-end is accessing the correct server URL and port. - Server Not Running: Double-check that your Node.js server is running without errors. Look for any error messages in the server’s console.
- Incorrect API Endpoint URLs: Ensure that the front-end JavaScript code is using the correct URLs for the API endpoints (e.g.,
http://localhost:3001/expenses). - Database Connection Errors: Check for errors during database connection or table creation in the server console. Ensure that the
sqlite3package is installed correctly. - Data Type Mismatches: In the front-end, make sure you are converting the amount to a number using
parseFloat()before sending it to the server. - Typos: Carefully check your code for any typos, especially in variable names, function names, and file paths.
Enhancements and Next Steps
This is a basic expense tracker. You can extend it in many ways:
- Implement Expense Editing and Deletion: Add functionality to edit or remove existing expenses.
- Add Categories: Allow users to categorize expenses (e.g., food, transportation, etc.).
- Implement Budgeting: Allow users to set budgets and track their spending against those budgets.
- Add Data Visualization: Use charting libraries (e.g., Chart.js) to visualize expenses.
- Improve User Interface (UI): Enhance the UI with more sophisticated styling, layouts, and user interactions.
- Add Authentication: Implement user authentication to secure the application.
- Deploy the Application: Deploy your application to a hosting platform like Heroku or Netlify.
Key Takeaways
- You’ve learned how to build a full-stack web application using Node.js, Express, and a simple database.
- You’ve gained experience with API design, database interaction, and front-end development.
- You’ve learned how to handle user input and display data dynamically.
- You’ve built a practical application that you can customize and expand upon.
Building this simple expense tracker provides a solid foundation for understanding web development concepts and building more complex applications. By working through this tutorial, you’ve not only created a useful tool but also expanded your skill set, empowering you to tackle more ambitious projects in the future. Remember that the journey of a thousand lines of code begins with a single step; keep experimenting, learning, and building!
