Expense Tracker JavaScript, PHP, MySQL

👤 Sharing: AI
```javascript
// JavaScript (Client-side) - HTML and JavaScript for the front-end
document.addEventListener("DOMContentLoaded", function() {
  const expenseForm = document.getElementById("expenseForm");
  const expenseList = document.getElementById("expenseList");
  const totalExpensesElement = document.getElementById("totalExpenses");

  let expenses = []; // Array to hold expense data
  let totalExpenses = 0;

  // Function to fetch expenses from the server
  async function fetchExpenses() {
    try {
      const response = await fetch("get_expenses.php"); // PHP file to fetch data
      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
      }
      const data = await response.json(); // Assuming PHP returns JSON
      expenses = data;
      updateExpenseList();
      updateTotalExpenses();

    } catch (error) {
      console.error("Error fetching expenses:", error);
      alert("Failed to fetch expenses. Check console for details.");
    }
  }


  // Function to add an expense to the server and local array
  async function addExpense(expense) {
    try {
      const response = await fetch("add_expense.php", { // PHP file to add data
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(expense),
      });

      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
      }

      const data = await response.json();  // Expecting the PHP to return the added expense with its ID

      expenses.push(data);  // add the *returned* expense.
      updateExpenseList();
      updateTotalExpenses();

    } catch (error) {
      console.error("Error adding expense:", error);
      alert("Failed to add expense. Check console for details.");
    }
  }

  // Function to delete an expense
  async function deleteExpense(id) {
    try {
        const response = await fetch(`delete_expense.php?id=${id}`, {  //PHP File to delete the expense by ID
            method: "DELETE",  // Use the DELETE HTTP method. More semantically appropriate
        });

        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }


        expenses = expenses.filter(expense => expense.id !== id);
        updateExpenseList();
        updateTotalExpenses();


    } catch (error) {
        console.error("Error deleting expense:", error);
        alert("Failed to delete expense. Check console for details.");
    }
  }



  // Function to update the expense list in the UI
  function updateExpenseList() {
    expenseList.innerHTML = ""; // Clear the list

    expenses.forEach(expense => {
      const listItem = document.createElement("li");
      listItem.innerHTML = `
        ${expense.description} - $${expense.amount} - ${expense.date}
        <button class="deleteButton" data-id="${expense.id}">Delete</button>
      `;
      expenseList.appendChild(listItem);
    });

    // Attach event listeners to the delete buttons
    const deleteButtons = document.querySelectorAll(".deleteButton");
    deleteButtons.forEach(button => {
      button.addEventListener("click", function() {
        const id = parseInt(this.dataset.id); //data-id attribute
        deleteExpense(id);
      });
    });
  }

  // Function to update the total expenses in the UI
  function updateTotalExpenses() {
    totalExpenses = expenses.reduce((sum, expense) => sum + parseFloat(expense.amount), 0);
    totalExpensesElement.textContent = totalExpenses.toFixed(2); // Format to 2 decimal places
  }

  // Event listener for the expense form submission
  expenseForm.addEventListener("submit", async function(event) {
    event.preventDefault(); // Prevent the default form submission

    const description = document.getElementById("description").value;
    const amount = document.getElementById("amount").value;
    const date = document.getElementById("date").value;

    if (description && amount && date) {
      const newExpense = {
        description: description,
        amount: parseFloat(amount),
        date: date,
      };

      await addExpense(newExpense); // Add the expense to the server and local array

      // Clear the form fields
      document.getElementById("description").value = "";
      document.getElementById("amount").value = "";
      document.getElementById("date").value = "";
    } else {
      alert("Please fill in all fields.");
    }
  });

  // Initial load of expenses when the page loads
  fetchExpenses();
});
```

```html
<!DOCTYPE html>
<html>
<head>
  <title>Expense Tracker</title>
  <style>
    body {
      font-family: sans-serif;
    }
    #expenseForm {
      margin-bottom: 20px;
    }
    #expenseList {
      list-style: none;
      padding: 0;
    }
    #expenseList li {
      border: 1px solid #ccc;
      padding: 10px;
      margin-bottom: 5px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .deleteButton {
      background-color: #f44336;
      color: white;
      border: none;
      padding: 5px 10px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-size: 12px;
      cursor: pointer;
    }
  </style>
</head>
<body>

  <h1>Expense Tracker</h1>

  <form id="expenseForm">
    <label for="description">Description:</label>
    <input type="text" id="description" name="description" required><br><br>

    <label for="amount">Amount:</label>
    <input type="number" id="amount" name="amount" step="0.01" required><br><br>

    <label for="date">Date:</label>
    <input type="date" id="date" name="date" required><br><br>

    <button type="submit">Add Expense</button>
  </form>

  <h2>Expenses:</h2>
  <ul id="expenseList"></ul>

  <h2>Total Expenses: $<span id="totalExpenses">0.00</span></h2>

  <script src="script.js"></script>  <!-- Link to the JavaScript file -->

</body>
</html>
```

```php
<?php
// PHP (Server-side) - Database connection and API endpoints

// Database configuration
$host = "localhost"; // Or your database host
$username = "your_username"; // Your database username
$password = "your_password"; // Your database password
$database = "expense_tracker"; // Your database name

// Create connection
$conn = new mysqli($host, $username, $password, $database);

// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

// Enable CORS (Cross-Origin Resource Sharing)
header("Access-Control-Allow-Origin: *"); // Allow requests from any origin (for development)
header("Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS"); // Allowed methods
header("Access-Control-Allow-Headers: Content-Type"); // Allowed headers

// Handle preflight requests (OPTIONS)
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
    http_response_code(200);
    exit;
}

// API Endpoints

// GET - Get all expenses
if ($_SERVER['REQUEST_METHOD'] == 'GET' && $_SERVER['REQUEST_URI'] == '/get_expenses.php') {
    $sql = "SELECT id, description, amount, date FROM expenses ORDER BY date DESC";
    $result = $conn->query($sql);

    $expenses = array();
    if ($result->num_rows > 0) {
        while($row = $result->fetch_assoc()) {
            $expenses[] = $row;
        }
    }

    header('Content-Type: application/json');
    echo json_encode($expenses);
}

// POST - Add a new expense
elseif ($_SERVER['REQUEST_METHOD'] == 'POST' && $_SERVER['REQUEST_URI'] == '/add_expense.php') {
    $data = json_decode(file_get_contents("php://input"));

    $description = $conn->real_escape_string($data->description);
    $amount = floatval($data->amount);
    $date = $conn->real_escape_string($data->date);

    $sql = "INSERT INTO expenses (description, amount, date) VALUES ('$description', $amount, '$date')";

    if ($conn->query($sql) === TRUE) {
      $new_expense_id = $conn->insert_id; // Get the ID of the inserted row
      //Retrieve the inserted expense, including its id.  Important for the javascript.
      $sql_get_expense = "SELECT id, description, amount, date FROM expenses WHERE id = $new_expense_id";
      $result = $conn->query($sql_get_expense);

      if ($result && $result->num_rows > 0) {
          $expense = $result->fetch_assoc();
          header('Content-Type: application/json');
          echo json_encode($expense);  // Return the new expense with its ID
      } else {
          http_response_code(500); // Internal Server Error
          echo json_encode(["error" => "Failed to retrieve newly created expense."]);
      }


    } else {
        http_response_code(500); // Internal Server Error
        echo json_encode(["error" => "Error: " . $sql . "<br>" . $conn->error]);
    }
}

// DELETE - Delete an expense by ID
elseif ($_SERVER['REQUEST_METHOD'] == 'DELETE' && strpos($_SERVER['REQUEST_URI'], '/delete_expense.php') !== false) {
    // Extract the ID from the query string
    $id = isset($_GET['id']) ? intval($_GET['id']) : 0;

    if ($id > 0) {
        $sql = "DELETE FROM expenses WHERE id = $id";

        if ($conn->query($sql) === TRUE) {
            http_response_code(200); // OK
            echo json_encode(["message" => "Expense deleted successfully"]);
        } else {
            http_response_code(500); // Internal Server Error
            echo json_encode(["error" => "Error deleting record: " . $conn->error]);
        }
    } else {
        http_response_code(400); // Bad Request
        echo json_encode(["error" => "Invalid ID provided"]);
    }
}

else {
    http_response_code(404); // Not Found
    echo json_encode(["error" => "Endpoint not found"]);
}

$conn->close();
?>
```

```sql
-- MySQL Database Setup

-- Create the database (if it doesn't exist)
CREATE DATABASE IF NOT EXISTS expense_tracker;

-- Use the database
USE expense_tracker;

-- Create the expenses table
CREATE TABLE IF NOT EXISTS expenses (
  id INT AUTO_INCREMENT PRIMARY KEY,
  description VARCHAR(255) NOT NULL,
  amount DECIMAL(10, 2) NOT NULL,
  date DATE NOT NULL
);

-- Optionally, insert some sample data
INSERT INTO expenses (description, amount, date) VALUES
('Groceries', 50.00, '2023-10-26'),
('Restaurant', 30.00, '2023-10-25'),
('Gas', 40.00, '2023-10-24');
```

**Explanation and How to Use:**

1.  **Database Setup (MySQL):**
    *   **Create Database:** The SQL code creates a database named `expense_tracker`.  If you already have a database, you can skip this.
    *   **Create Table:**  It then creates a table named `expenses` with columns:
        *   `id`:  Unique identifier for each expense (auto-incrementing integer).
        *   `description`:  A string describing the expense.
        *   `amount`:  The expense amount (decimal with 2 decimal places).
        *   `date`:  The date of the expense.
    *   **Sample Data (Optional):** The `INSERT` statements add some example data to the table. You can modify or remove these.
    *   **Important:**  Use a MySQL client (like phpMyAdmin, MySQL Workbench, or the MySQL command-line tool) to run this SQL code on your MySQL server.

2.  **PHP (Server-side):**
    *   **Database Connection:**
        *   The PHP code connects to your MySQL database using `$host`, `$username`, `$password`, and `$database`.  **Replace these with your actual database credentials.**
    *   **CORS Headers:**
        *   The `header()` functions set CORS (Cross-Origin Resource Sharing) headers.  This is essential because your JavaScript front-end will be running in a browser, and browsers restrict requests to different domains (origins) for security reasons.  `Access-Control-Allow-Origin: *` allows requests from *any* origin, which is convenient for development but **should be restricted in production** to only the domain(s) where your front-end is hosted.
        *   `Access-Control-Allow-Methods` specifies the HTTP methods allowed (GET, POST, DELETE, OPTIONS).
        *   `Access-Control-Allow-Headers` specifies the headers allowed in the requests.
    *   **API Endpoints:** The PHP code defines three API endpoints:
        *   **`GET /get_expenses.php`:**  Fetches all expenses from the `expenses` table and returns them as a JSON array.
        *   **`POST /add_expense.php`:**  Adds a new expense to the `expenses` table.  It reads the expense data from the request body (which should be in JSON format).  It then returns the newly created expense including the assigned ID, which is important for updating the front-end.
        *   **`DELETE /delete_expense.php?id=...`:**  Deletes an expense from the `expenses` table based on its `id`.
    *   **Error Handling:** The PHP code includes basic error handling (checking for database connection errors and query errors).  It also returns HTTP error codes (e.g., 500 for internal server error, 400 for bad request, 404 for not found) and JSON responses with error messages when something goes wrong.
    *   **Security:**
        *   The code uses `$conn->real_escape_string()` to escape user input (the `description` and `date` fields) before inserting it into the database. This is *crucial* to prevent SQL injection vulnerabilities. Never trust user input directly in your database queries.
        *   It's also important to validate and sanitize user input on both the client-side (JavaScript) and the server-side (PHP).
        *  Use prepared statements with parameterized queries for even stronger SQL injection protection, though this example skips that for brevity.
    *   **Deployment:** Save the PHP code in a file named `get_expenses.php`, `add_expense.php`, and `delete_expense.php` (or whatever you named them) on your web server in a directory accessible by the web server. Make sure the PHP files can be executed.

3.  **JavaScript (Client-side):**
    *   **HTML Elements:** The HTML provides the form for adding expenses (`#expenseForm`), a list to display expenses (`#expenseList`), and an element to show the total expenses (`#totalExpenses`).
    *   **Event Listener:**  The JavaScript code attaches an event listener to the `DOMContentLoaded` event to ensure that the script runs after the HTML document has been fully loaded.
    *   **Functions:**
        *   `fetchExpenses()`: Fetches expenses from the server (using the `get_expenses.php` endpoint) and updates the UI.
        *   `addExpense(expense)`: Sends a POST request to the `add_expense.php` endpoint to add a new expense to the database.
        *   `deleteExpense(id)`: Sends a DELETE request to the `delete_expense.php` endpoint to delete an expense.
        *   `updateExpenseList()`: Updates the HTML list with the current expenses.
        *   `updateTotalExpenses()`: Calculates the total expenses and updates the total expenses element in the UI.
    *   **Form Submission:**  The JavaScript code also attaches an event listener to the expense form's `submit` event. When the form is submitted:
        *   It prevents the default form submission behavior (which would cause the page to reload).
        *   It gets the values from the form fields.
        *   It calls `addExpense()` to send the new expense to the server.
        *   It clears the form fields.
    *   **AJAX:** The JavaScript code uses the `fetch` API to make asynchronous HTTP requests (AJAX) to the PHP endpoints. This allows the front-end to communicate with the server without reloading the page.
    *   **Error Handling:**  The `fetch` calls include `try...catch` blocks to handle potential errors during the AJAX requests.

4. **HTML (index.html):**
    *   Basic HTML structure with a form for adding expenses, a list for displaying expenses, and a total expenses display.
    *   Links to the JavaScript (`script.js`) file.  Make sure this file is in the same directory as your HTML file.
    *   Simple CSS styling for basic presentation.

**Steps to Run:**

1.  **Install a Web Server:** You'll need a web server (like Apache or Nginx) with PHP and MySQL enabled.  If you're using a development environment, consider using XAMPP, WAMP, or MAMP, which bundle everything together.
2.  **Database Setup:**
    *   Create the `expense_tracker` database in MySQL.
    *   Execute the SQL code from the `MySQL Database Setup` section to create the `expenses` table.
3.  **Configure PHP:**
    *   Update the PHP code in the `get_expenses.php`, `add_expense.php`, and `delete_expense.php` files with your database credentials.
    *   Place these PHP files in a directory accessible by your web server (e.g., the `htdocs` directory in XAMPP).
4.  **Configure JavaScript/HTML:**
    *   Save the JavaScript code in a file named `script.js` in the same directory as your HTML file.
    *   Save the HTML code in a file named `index.html` (or any name you prefer) in the same directory.
5.  **Access the Application:** Open your web browser and navigate to the URL where your `index.html` file is located (e.g., `http://localhost/expense_tracker/index.html`).

**Important Considerations:**

*   **Security:** This is a very basic example and lacks many security features that would be necessary for a production application.  Address these before deploying:
    *   **Input Validation:**  Validate all user input on both the client-side and server-side to prevent malicious data from being stored in the database.
    *   **Output Encoding:** Encode data before displaying it in the UI to prevent cross-site scripting (XSS) vulnerabilities.
    *   **Authentication and Authorization:** Implement user authentication (e.g., using usernames and passwords) and authorization (controlling who can access what) to protect your data.
    *   **HTTPS:** Use HTTPS to encrypt communication between the client and the server.
    *   **Rate Limiting:** Implement rate limiting to prevent abuse of your API endpoints.
    *   **Prepared Statements:** As mentioned before, use prepared statements with parameterized queries for the best SQL injection prevention.
*   **Error Handling:** Improve the error handling to provide more informative error messages to the user and log errors for debugging.
*   **User Interface:** Enhance the user interface with more features and better styling.
*   **Scalability:** If you expect a large number of users, consider using a more scalable architecture (e.g., a load balancer, multiple web servers, a database cluster).
*   **Modern JavaScript:** Consider using a JavaScript framework like React, Angular, or Vue.js for a more structured and maintainable front-end.

This comprehensive example provides a solid foundation for building a more advanced expense tracker application.  Remember to prioritize security and scalability as you develop your application further.
👁️ Viewed: 9

Comments