0% found this document useful (0 votes)
32 views

ExpenseTracker So Far

Uploaded by

dcsanatnaik
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
32 views

ExpenseTracker So Far

Uploaded by

dcsanatnaik
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 20

Frontend:

import React, {useState} from 'react';


import { addExpense } from '../redux/expensesSlice';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { addNewExpense } from '../services/expenseService';
import { useDispatch } from 'react-redux';
import "../styles/AddExpenseForm.css"

const AddExpenseForm = () => {


const [description, setDescription] = useState('');
const [amount, setAmount] = useState('');
const dispatch = useDispatch(); // initialize the redux dispatch function,
allows us to send actions to redux store.
const queryClient = useQueryClient();

//mutation to add a new expense


const addMutation = useMutation({
mutationFn: addNewExpense, // addNewExpense function simulates a server-
side API call to add an expense
onSuccess: (newExpense) => {
queryClient.invalidateQueries(['expenses']); // Refetch expenses to
keep the UI updated
dispatch(addExpense(newExpense)); // Dispatches the addExpense action
to Redux
},
});

const handleSubmit = async (e) => {


e.preventDefault();

if (!description.trim()) {
window.alert("Description can't be empty");
return;
}

if (!amount.trim()) {
window.alert("Amount can't be empty");
return;
}

// Confirm adding expense


const isConfirmed = window.confirm("Are you sure you want to add this
expense?");
if (!isConfirmed) return;

if (description && amount) {


const newExpense = {
description: description, // match the backend field
amount: parseFloat(amount), // ensure it's a number
date: new Date().toISOString(), // ensrue a valid date
};

try {
await addMutation.mutateAsync(newExpense);
console.log("Expense added:", newExpense);
} catch (error) {
console.error("Error adding expense:", error);
}
setDescription("");
setAmount("");
}else {
console.error("Validation error: Name or amount is missing");
}
};

return(
<div className="add-expense-container">
<div className="add-expense-form-wrapper">
<h2>Add New Expense</h2>
<form onSubmit={handleSubmit}>
<input
className="add-expense-input"
type="text"
value={description}
onChange={(e)=>setDescription(e.target.value)}
placeholder='Description'
/>
<input
className="add-expense-input"
type="number"
value={amount}
onChange={(e)=>setAmount(e.target.value)}
placeholder='Amount'
/>
<button className="add-expense-button" type="submit"> Add
Expense</button>
</form>
</div>
</div>
);
};

export default AddExpenseForm;

import React from 'react';


import { useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { useQueryClient } from '@tanstack/react-query';
import { PowerOff } from 'lucide-react';
import '../Styles/LogoutButton.css'

const LogoutButton = () => {


const navigate = useNavigate();
const dispatch = useDispatch();
const queryClient = useQueryClient();

const handleLogout = () => {


// confirmation dialogbix
const isConfirmed = window.confirm('Are you sure you want to logout?');

// proceed with logout if user confirms logout


if (isConfirmed) {
localStorage.removeItem('authToken');// Clear authentication token from
localStorage
queryClient.clear(); // Clear all queries to remove cached data
dispatch({ type: 'expenses/reset' });// Clear expenses state
navigate('/login', {
replace: true,
state: { preventBack: true }
});// Prevent going back to dashboard after logout
window.history.pushState(null, '', '/login');// Additional step to
prevent browser back button access
}
};

return (
<button
className="logout-button"
onClick={handleLogout}
title='Logout'
>
<PowerOff size={24} />
</button>
);
};

export default LogoutButton;

import React, { useEffect, useState } from "react";


import { useDispatch } from "react-redux";
import { updateExpense } from "../redux/expensesSlice";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import "../styles/UpdateExpenseForm.css"

const UpdateExpenseForm = ({expense, onCancel}) => {


const [description, setDescription] = useState(expense.description);
const [amount, setAmount] = useState(expense.amount);
const dispatch = useDispatch();
const queryClient = useQueryClient();

const mutation = useMutation({


mutationFn: async (updatedExpense) => {
const token = localStorage.getItem('authToken');

const response = await fetch(`http://localhost:5000/api/expenses/$


{updatedExpense._id}`, { // use the backend port here
method: 'PUT',
headers: {
'Content-Type': 'application/json',// indicates that the
request body will be in JSON format since the backend expects JSON data for correct
parsing.
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
description: updatedExpense.description,
amount: updatedExpense.amount,
date: updatedExpense.date
}),
});

if (!response.ok) {
// Handle the 404 error here
if (response.status === 404) {
throw new Error('Expense not found');
} else {
throw new Error('Failed to update expense');
}
}
return response.json();
},
onSuccess: (updatedExpense) => {
queryClient.invalidateQueries(['expenses']); // This refetches the
'expenses' query
dispatch(updateExpense(updatedExpense)); // Update Redux store if
needed
onCancel(); //closes the update option once user clicks update expense
button
},
onError: (error) => {
console.error('Update Expense Error:', error);
},
});

const handleSubmit = (e) => {


e.preventDefault();

if (!description || !description.toString().trim()) {
window.alert("Description can't be empty");
return;
}

// first we convert the amount to string to use trim() since it doesnt work
on numbers
const amountStr = amount.toString();
if (!amountStr.trim()) {
window.alert("Amount can't be empty");
return;
}

const isConfirmed = window.confirm("Are you sure you want to update?");


if (!isConfirmed) return;

const updatedExpense = {
...expense,
description,
amount: parseFloat(amount),
};
mutation.mutate(updatedExpense); //triggers the mutation(update action) to
send the updated expense to the backend.
};

useEffect(()=>{
setDescription(expense.description);
setAmount(expense.amount);
}, [expense]);

return(
<div className="update-expense-container">
<div className="update-expense-form-wrapper">
<h2>Update Expense</h2>
<form onSubmit={handleSubmit}>
<input
className="update-expense-input"
type="text"
value={description}
onChange={(e)=>setDescription(e.target.value)}
/>
<input
className="update-expense-input"
type="number"
value={amount}
onChange={(e)=>setAmount(e.target.value)}
/>
<div className="update-expense-button-wrapper">
<button
className="update-expense-button"
type="submit">
Update
</button>

<button
className="cancel-button"
type="button"
onClick={onCancel}>
Cancel
</button>
</div>
</form>
</div>
</div>
);
};

export default UpdateExpenseForm;

import React, { useEffect, useMemo, useState } from "react";


import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { deleteExpense, getExpenses } from "../services/expenseService";
import { useDispatch } from "react-redux";
import AddExpenseForm from "../Components/AddExpenseForm.jsx"
import UpdateExpenseForm from "../Components/UpdateExpenseForm.jsx";
import { removeExpense } from "../redux/expensesSlice.js";
import LogoutButton from "../Components/LogoutButton.jsx";
import { useNavigate } from "react-router-dom";
import { Pencil, Trash2 } from 'lucide-react';
import '../styles/Dashboard.css'

const CURRENCY_SYMBOLS = {
'USD': '$',
'INR': '₹',
'EUR': '€',
'GBP': '£',
'JPY': '¥'
};

const Dashboard = () => {


const navigate = useNavigate();

const [editingExpense, setEditingExpense] = useState(null); //to track the


expense thats being edited
const [selectedCurrency, setSelectedCurrency] = useState(() => {
return localStorage.getItem('selectedCurrency') || 'INR';
});

const isAuthenticated = () => !!localStorage.getItem('authToken');

// authentication and navigation effect


useEffect(() => {
// redirect to login if not authenticated
if (!isAuthenticated()) {
window.history.replaceState(null, '', '/login');
navigate('/login', { replace: true });
return;
}

// prevent the user from accessing dashboard after logout


const handlePopstate = () => {
if (!isAuthenticated()) {
window.history.replaceState(null, '', '/login');
navigate('/login', { replace: true });
}
};

// Add popstate event listener


window.addEventListener('popstate', handlePopstate);

// Cleanup
return () => {
window.removeEventListener('popstate', handlePopstate);
};
}, [navigate]);

const { data: expenses, isLoading, error } = useQuery({


queryKey: ['expenses'],
queryFn: getExpenses, // function to fetch expenses
enabled: isAuthenticated() // Only fetch if authenticated
});

//to calculate the total expense


const totalExpenses = useMemo(()=>{
return expenses
? expenses.reduce((total, expense)=>total +
parseFloat(expense.amount),0)
: 0;
}, [expenses]);

//created the delete mutation here instead of a separate component like add and
update
const dispatch = useDispatch();
const queryClient = useQueryClient();

const deleteMutation = useMutation({


mutationFn: deleteExpense, // Explicitly set the function
onSuccess: (deletedExpenseID) => {
queryClient.invalidateQueries(['expenses']);
dispatch(removeExpense(deletedExpenseID));
},
});
const handleDelete = (id) =>{
const confirmDelete = window.confirm("Are you sure you want to delete?");
if (confirmDelete) {
deleteMutation.mutate(id);
}
}

const handleEdit = (expense) => { //set the expense to be edited


setEditingExpense(expense);
}

const handleCancelEdit = () =>{ //clear the editing state


setEditingExpense(null);
}

const handleCurrencyChange = (event) => {


const newCurrency = event.target.value;
setSelectedCurrency(newCurrency);
localStorage.setItem('selectedCurrency', newCurrency);// Save the
selected currency to localStorage
}
// Additional authentication check
if (!isAuthenticated()) {
return null; // or redirect component
}

if(isLoading) return <div>Loading...</div>


if(error) return <div>Error Fetching Expenses</div>

return (
<div className="dashboard-container">
<div className="dashboard-header">
<div className="total-expenses">
Total Expenses: {CURRENCY_SYMBOLS[selectedCurrency]}
{totalExpenses.toFixed(2)}
</div>
<div className="currency-selector">
<label htmlFor="currency-select">Currency: </label>
<select
id="currency-select"
value={selectedCurrency}
onChange={handleCurrencyChange}
>
{Object.keys(CURRENCY_SYMBOLS).map(currency => (
<option key={currency} value={currency}>
{currency} ({CURRENCY_SYMBOLS[currency]})
</option>
))}
</select>
</div>
<LogoutButton />
</div>

<h1>Expense Tracker Dashboard</h1>


{!editingExpense && (
<div className="dashboard-content">
<AddExpenseForm />
</div>
)}

{editingExpense ? (
<div>
<UpdateExpenseForm expense={editingExpense}
onCancel={handleCancelEdit} />
</div>
) : (
expenses && expenses.length > 0 ? ( // check if expenses is defined
<table className="expenses-table">
<thead>
<tr>
<th>Description</th>
<th>Amount</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{expenses.map((expense) => (
<tr key={expense._id}>
<td data-
label="Description">{expense.description}</td>
<td data-label="Amount">
{CURRENCY_SYMBOLS[selectedCurrency]}
{expense.amount}
</td>
<td data-label="Actions">
<div className="action-icons">
<Pencil
className="edit-icon"
size={20}
onClick={() => handleEdit(expense)}
title="Edit Expense"
/>
<Trash2
className="delete-icon"
size={20}
onClick={() =>
handleDelete(expense._id)}
title="Delete Expense"
/>
</div>
</td>
</tr>
))}
</tbody>
</table>
) : (
<div className="no-expenses">
<p>No expenses added yet.</p>
</div>
)
)}
</div>
);

export default Dashboard;


import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { loginUser } from "../services/authService";
import { useQueryClient } from "@tanstack/react-query";
import '../styles/Login.css'

const Login = () => {


const [formData, setFormData] = useState({ username: "", password: "" });
const [error, setError] = useState("");
const navigate = useNavigate();
const queryClient = useQueryClient();

const handleChange = (e) => {


setFormData({ ...formData, [e.target.name]: e.target.value });
};

const handleSubmit = async (e) => {


e.preventDefault();
setError("");

try {

const token = await loginUser(formData);

if (token) {
localStorage.setItem("token", token);// Store the token in localStorage
queryClient.invalidateQueries("expenses");// Auto-refresh data after login
navigate("/dashboard");
} else {
setError("Login failed. Please try again.");
}

const response = await fetch("http://localhost:5000/api/auth/login", {


method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData),
});

const data = await response.json();

if (response.ok) {
localStorage.setItem("token", data.token); // Store the token in
localStorage
navigate("/dashboard");
} else {
setError(data.message || "Login failed. Please try again.");
}
} catch (err) {
setError("An error occurred. Please try again.");
}
};

return (
<div className="login-container">
<div className="login-form">
<h2>Login</h2>
<form onSubmit={handleSubmit}>
<div>
<input
className="login-input"
type="text"
name="username"
placeholder="Username"
value={formData.username}
onChange={handleChange}
required
/>
</div>
<div>

<input
className="login-input"
type="password"
name="password"
placeholder="Password"
value={formData.password}
onChange={handleChange}
required
/>
</div>
<button className="login-button" type="submit">Login</button>
</form>
{error && <p style={{ color: "red" }}>{error}</p>}
<div className="register-link">
<Link to="/register">New user? Click here to register</Link>
</div>
</div>
</div>
);
};

export default Login;

import React, { useState } from "react";


import { Link, useNavigate } from "react-router-dom";
import '../styles/Register.css';

const Register = () => {


const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const navigate = useNavigate();

const handleSubmit = async (e) => {


e.preventDefault();

if (username.length < 2) {
setError("Username cannot be less than 2 characters");
return;
}

if (password.length < 8) {
setError("Password cannot be less than 8 characters");
return;
}
try {
const response = await fetch("http://localhost:5000/api/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});

const data = await response.json();

if (response.ok) {
alert("Registration successful. Redirecting to login.");
navigate("/login"); // Redirect to login page (replace with your method)
} else {
if (data.message === "User already exists") {
setError("Username you entered is taken");
} else {
setError(data.message || "Error registering user");
}
}
} catch (error) {
console.error("Error:", error);
}
};

return (
<div className="register-container">
<div className="register-form">
<h2>Register</h2>
{error && <div className="error-message">{error}</div>}
<form onSubmit={handleSubmit}>
<input
className="register-input"
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
<input
className="register-input"
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button className="register-button"type="submit">Register</button>
</form>
<div className="login-link">
<Link to="/login" >
Already registered? Click here to login
</Link>
</div>
</div>
</div>
);
};

export default Register;


Redux folder:
import { createSlice } from "@reduxjs/toolkit";

const expenseSlice = createSlice({


name:'expenses',
initialState: [],
reducers: {
addExpense: (state, action) => {
state.push(action.payload)
},
removeExpense: (state, action) => {
return state.filter((expense)=>expense._id !== action.payload)
},
updateExpense: (state, action) => {
const index = state.findIndex((expense)=>expense.id ===
action.payload.id);
if (index !== -1){
state[index] = action.payload;
}
},
reset: () => []
}
})

export const { addExpense, removeExpense, updateExpense, reset } =


expenseSlice.actions;
export default expenseSlice.reducer;

import { configureStore } from "@reduxjs/toolkit";


import expensesReducer from './expensesSlice';

const store = configureStore({


reducer: {
expenses: expensesReducer
}
})

export default store;

Services folder:
import axios from 'axios';

export const loginUser = async (credentials) => {


try {
const response = await axios.post('http://localhost:5000/api/auth/login',
credentials);
const token = response.data.token;

if (token) {
localStorage.setItem('authToken', token); // Save the token
} else {
console.error('Login failed: No token received.');
}

return token; // Return the token for immediate use, if needed


} catch (error) {
console.error('Login error:', error.message);
throw error; // Re-throw to handle in the calling component
}
};

export const logoutUser = () => {


localStorage.removeItem("authToken");
};

import axios from 'axios';

export const getExpenses = async () => {


const token = localStorage.getItem('token'); //store the JWT in localStorage
const response = await fetch('http://localhost:5000/api/expenses', {
headers: {
Authorization: `Bearer ${token}`, // Add Authorization header
},
});

if (!response.ok) {
throw new Error('Failed to fetch expenses');
}

return response.json();
};

export const addNewExpense = async (expense) => {


const token = localStorage.getItem('authToken'); //store the token in
localstorage
if (!token) {
throw new Error('No authentication token found');
}

try {
const response = await axios.post(
'http://localhost:5000/api/expenses',
expense,
{
headers: {
Authorization: `Bearer ${token}`, // Attach token
},
}
);
return response.data;
} catch (error) {
console.error("Error in addNewExpense:", error.response?.data ||
error.message);
throw new Error("Failed to add expense");
}
};

export const deleteExpense = async (id) => {


const token = localStorage.getItem('authToken');
if (!token) {
throw new Error('No authentication token found');
}

const response = await fetch(`http://localhost:5000/api/expenses/${id}`, {


method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}` // Add authorization header
},

});
if (!response.ok) {
throw new Error("Failed to delete expense");
}
return id; // returning id as resolved value after successful delete
};

export const updateExpenseAPI = async(updatedExpense) =>{


const token = localStorage.getItem('authToken');
const response = await fetch(`http://localhost:5000/api/expenses/$
{updatedExpense.id}`,{
method:'PUT',
headers: {
'Content-Type':'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
description: updatedExpense.description,
amount: updatedExpense.amount,
date: updatedExpense.date
})

});
if (!response.ok){
throw new Error("Failed to update expense");
}
return response.json(); //will return the updated expense from backend.
}

main.jsx:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './App';
import store from './redux/store';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</Provider>
);

App.jsx:
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Register from "./Pages/Register.jsx";
import Dashboard from "./Pages/Dashboard.jsx";
import Login from "./Pages/Login.jsx";
function App() {
return (
<Router>
<Routes>
<Route path="/register" element={<Register />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/" element={<Register />} /> {/* Default route */}
</Routes>
</Router>
);
}

export default App;

Backend:

Controllers folder:
import Expense from "../models/Expense.js";

let expenses = []; //temp in-memory storage

// export const getAllExpenses = (req, res) =>{


// res.json(expenses);
// };

export const getAllExpenses = async(req, res) => {


try {
const expenses = await Expense.find({userId: req.user.id});
res.json(expenses)
} catch (error) {
console.error("Error fetching expenses:", error);
res.status(500).json({ message: 'Error fetching expense '})
}
};

// export const addExpense = (req, res) =>{


// const expense = req.body;
// expenses.push(expense);
// res.status(201).json(expense);
// };

//after setting up mongo:


export const addExpense = async(req, res) =>{
const { amount, description, date }= req.body;
console.log("Received data:", req.body);

if (!description || typeof description !== "string") {


return res.status(400).json({ message: "Invalid or missing description" });
}

if (amount === undefined || typeof amount !== "number" || amount <= 0) {


return res.status(400).json({ message: "Invalid or missing amount" });
}

if (!date || isNaN(new Date(date))) {


return res.status(400).json({ message: "Invalid or missing date" });
}
try {

const expense = new Expense({


userId: req.user.id, //associate the expense with the user thats logged
in
description,
amount,
date,
});

await expense.save();
res.status(201).json(expense);

} catch (error) {
console.error("Error adding expense:", error);
res.status(400).json({message: 'Error adding expense'})
}
};
//

export const updateExpense = async(req, res) => {


const { id } = req.params;
const { amount, description, date} = req.body;

if (description && typeof description !== "string") {


return res.status(400).json({ message: "Invalid description" });
}

if (amount !== undefined && (typeof amount !== "number" || amount <= 0)) {
return res.status(400).json({ message: "Invalid amount" });
}

if (date && isNaN(new Date(date))) {


return res.status(400).json({ message: "Invalid date" });
}

try{
const expense = await Expense.findOneAndUpdate(
{_id: id, userId: req.user.id},
{ amount, description, date },
{ new: true}
);
if (!expense) {
return res.status(404).json({ message: 'Expense not found' });
}
res.status(200).json(expense);
} catch (error) {
res.status(400).json({ message: 'Error updating expense' });
}
};

export const deleteExpense = async(req, res) => {


const { id } = req.params;
try{
const expense = await Expense.findOneAndDelete({ _id: id, userId:
req.user.id});
if (!expense){
return res.status(404).json({message: 'Expense Not Found'});
}
res.status(200).json({message: 'Expense Delted'});
}catch(error){
res.status(500).json({message: 'Error occured deleting'});
}
}

Middleware folder:
import jwt from "jsonwebtoken";
import dotenv from "dotenv";
dotenv.config();

const secret = process.env.JWT_SECRET; //secret is needed to verify tokens later,


ensuring they haven’t been tampered with.

export const authenticateToken = (req, res, next) => {


const authHeader = req.headers['authorization'];
console.log("Authorization Header:", authHeader); // check if the header exists

const token = authHeader?.split(' ')[1];


console.log("Token Extracted:", token); // verify if the token is correctly
extracted

if (!token) {
console.log("Token missing. Sending 401.");
return res.sendStatus(401); // Unauthorized
}

jwt.verify(token, secret, (err, user) => {


if (err) {
console.log("JWT Verification Error:", err.message); // Log JWT errors
return res.sendStatus(403); // Forbidden
}

console.log("JWT Verified Successfully. User:", user); // Log the decoded


user
req.user = user; // Attach user info to the request
next();
});
};

Models folder:

import mongoose from 'mongoose';

const expenseSchema = new mongoose.Schema({


userId:{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},

description:{
type: String,
required: true,
},

amount:{
type: Number,
required: true,
},
date:{
type: Date,
default: Date.now,
},
});

const Expense = mongoose.model('Expense', expenseSchema);


export default Expense;

import mongoose from "mongoose";

const userSchema = new mongoose.Schema({


username: {
type: String,
required: true,
unique: true,
},
password:{
type: String,
required: true,
},

})

const User = mongoose.model('User', userSchema);


export default User;

Routes folder:

import dotenv from 'dotenv';


dotenv.config();
import express from 'express';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import User from '../models/User.js';

const router = express.Router();


const secret = process.env.JWT_SECRET;

console.log('JWT Secret:', secret);

router.post('/register', async(req, res)=>{


const { username, password } = req.body;
const hashedPassword = await bcrypt.hash(password,10);
const user = new User({username, password:hashedPassword});

try {
await user.save();
res.status(201).json({message: 'User Created'});
} catch (error) {
res.status(400).json({message: 'User already exists'})

}
});

//user login
router.post('/login', async(req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });

if (!user || !(await bcrypt.compare(password, user.password))) {


return res.status(401).json({ message: 'Invalid Credentials' });
}

const token = jwt.sign({ id: user._id }, secret, { expiresIn: '1h' }); // This
line uses the secret
res.json({ token });
});

export default router;

// expenseRoutes.js
import express from 'express';
import { getAllExpenses, addExpense, deleteExpense, updateExpense } from
'../controllers/expenseController.js';
import { authenticateToken } from '../middleware/authMiddleware.js';

const router = express.Router();

router.get('/', authenticateToken, getAllExpenses);


router.post('/', authenticateToken, addExpense);
router.delete('/:id',authenticateToken, deleteExpense);
router.put('/:id', authenticateToken, updateExpense);

export default router;

server.js:
import dotenv from 'dotenv';
dotenv.config();
import express from 'express';
import cors from 'cors';
import expenseRoutes from './routes/expenseRoutes.js';
import mongoose from 'mongoose';
import authRoutes from './routes/authRoutes.js';

const app = express();

//mongodb connection
const mongoURI = 'mongodb://localhost:27017/expense_tracker';
mongoose.connect(mongoURI)
.then(()=>console.log("MongoDB Connected"))
.catch(err=>console.error("MongoDB Connection Error", err));

app.use(cors()); //enable CORS


app.use(express.json());

//routes
app.use('/api/expenses', expenseRoutes);
app.use('/api/auth', authRoutes) //add authentication routes

const PORT = process.env.PORT || 5000;


app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});

.env file:
JWT_SECRET=4b156fef45091b0a2c86476a5af1f7c825de4bb2268ae6687ede2aca67dc36588ae3b982
b471539ef183787b05717359bb839eb2d3e4fcb92fefbc97724ca035

You might also like