Building an EMI Calculator Web Application with FastAPI and Alpine.js
Tags: fastapi, alpinejs, webdev, tutorial
Published: 2023-06-25 10:47:48


In these tutorials, we have seen [how to create an API for EMI calculator using FastAPI](https://dev.to/abbazs/how-to-create-an-emi-calculator-api-using-fastapi-h14) and [how to create a standalone web page that can calculate EMI using Alpine.js](https://dev.to/abbazs/how-to-build-a-emi-calculator-using-alpinejs-404). In this tutorial, we are going to combine FastAPI and Alpine.js to create a web application with an Alpine.js frontend.

If you observe the stand alone tutorial we were calculating only the EMI. Other values also can be calculated writing the respective functions in javascript itself. Here we are repurposing the existing FastAPI API and Alpine.js standalone page, we will learn how to combine these technologies to create a web application that offers both backend functionality and frontend interactivity.

## Repurposing the Existing FastAPI

The existing FastAPI code consists of the basic setup for creating a FastAPI application. It includes importing the necessary modules and creating an instance of the FastAPI app.

```python
from fastapi import FastAPI
import numpy_financial as npf

app = FastAPI()
```

To repurpose the existing FastAPI application to serve an HTML file, we need to add additional code that handles static file serving and sets up the root endpoint ("/") to return the index.html file.

First, we import the required modules for serving static files and handling file responses:

```python
from fastapi import FastAPI
from starlette.responses import FileResponse
from fastapi.staticfiles import StaticFiles
import numpy_financial as npf
import numpy as np
```

Next, we mount a new route ("/static") in our FastAPI app using the `app.mount()` method. This route is responsible for serving static files such as CSS, JavaScript, and images. We specify the directory where our static files are located using the `directory` parameter. In this example, the static files are stored in the "static" directory.

```python
app.mount("/static", StaticFiles(directory="static"), name="static")
```

After setting up the static file serving, we define a new endpoint for the root URL ("/") using the `@app.get` decorator. We pass the `response_class=FileResponse` parameter to specify that the response should be treated as a file response.

Inside the endpoint function, we use the `FileResponse` class to create a file response for the "static/index.html" file. This means that when a user visits the root URL of our web application, FastAPI will serve the index.html file located in the "static" directory.

```python
@app.get("/", response_class=FileResponse)
async def read_index():
    return FileResponse("static/index.html")
```

By adding this code, we have repurposed the existing FastAPI application to serve the index.html file and any other static files required by our web application.

In addition to serving the HTML file, we also need to modify the existing API endpoints to return all the values required for the integration with Alpine.js. In the original code, only the calculated EMI value was returned. Now, we include the principal, tenure, rate of interest, and EMI values in the response.

Here's an example of modifying the "/calculate-emi" endpoint:

```python
@app.get("/calculate-emi")
async def get_emi(
    principal: float = 100000,
    tenure: int = 6,
    rate_of_interest: float = 12,
):
    emi = npf.pmt(rate_of_interest / 100 / 12, tenure, -principal, when="end")
    context = {
        "principal": principal,
        "tenure": tenure,
        "rate_of_interest": rate_of_interest,
        "emi": round(emi, 2),
    }
    return context
```

Similarly, you need to modify the code of other endpoints ("/calculate-tenure", "/calculate-principal", and "/calculate-rate") to include the necessary values in the response.

After integrating the modifications, the final code will look like this:

```python
from fastapi import FastAPI
from starlette.responses import FileResponse
from fastapi.staticfiles import StaticFiles
import numpy_financial as npf
import numpy as np

app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")


@app.get("/", response_class=FileResponse)
async def read_index():
    return FileResponse("static/index.html")


@app.get("/calculate-emi")
async def get_

emi(
    principal: float = 100000,
    tenure: int = 6,
    rate_of_interest: float = 12,
):
    emi = npf.pmt(rate_of_interest / 100 / 12, tenure, -principal, when="end")
    context = {
        "principal": principal,
        "tenure": tenure,
        "rate_of_interest": rate_of_interest,
        "emi": round(emi, 2),
    }
    return context


@app.get("/calculate-tenure")
async def get_tenure(
    principal: float = 100000,
    emi: float = 17200,
    rate_of_interest: float = 12,
):
    tenure = npf.nper(rate_of_interest / 100 / 12, -emi, principal, when="end")
    context = {
        "principal": principal,
        "emi": emi,
        "rate_of_interest": rate_of_interest,
        "tenure": np.round(tenure, 0),
    }
    return context


@app.get("/calculate-principal")
async def get_principal(
    tenure: int = 6, emi: float = 17200, rate_of_interest: float = 12
):
    principal = npf.pv(rate_of_interest / 100 / 12, tenure, -emi, when="end")
    context = {
        "tenure": tenure,
        "emi": emi,
        "rate_of_interest": rate_of_interest,
        "principal": np.round(principal, 2),
    }
    return context


@app.get("/calculate-rate")
async def get_rate(principal: float = 100000, tenure: int = 6, emi: float = 17200):
    rate = npf.rate(tenure, -emi, principal, 0, when="end") * 12 * 100
    context = {
        "principal": principal,
        "tenure": tenure,
        "emi": emi,
        "rate_of_interest": np.round(rate, 0),
    }
    return context
```

With these modifications, the FastAPI application can serve the HTML file and provide the necessary values for integration with Alpine.js, enabling the creation of a web application that combines the backend functionality of FastAPI with the frontend interactivity of Alpine.js.

## Repurposing the Existing Frontend

In order to repurpose the existing frontend code, we need to make some changes to enhance its functionality and enable communication with the FastAPI backend. To achieve this, we will introduce the use of Axios, a popular JavaScript library for making HTTP requests to APIs. Additionally, we will modify the data structure to align with the naming conventions commonly used in Python and FastAPI.

### Existing code

```html
<script>
  const app = () => ({
    input_data: {
      Principal: 100000,
      RateOfInterest: 12,
      Tenure: 6,
      EMI: 0.0,
    },
    output_data: { EMI: 0, Instalments: [] },
    state: { activeButton: null },
    TPAY: function () {
      const total_payment = this.input_data.EMI * this.input_data.Tenure || 0;
      return total_payment.toFixed(2);
    },
    calculateEMI: function () {
      const principal = parseFloat(this.input_data.Principal);
      const rateOfInterest = parseFloat(this.input_data.RateOfInterest);
      const tenure = parseInt(this.input_data.Tenure);

      if (!isNaN(principal) && !isNaN(rateOfInterest) && !isNaN(tenure)) {
        const monthlyInterest = rateOfInterest / 12 / 100;
        const emi =
          (principal *
            monthlyInterest *
            Math.pow(1 + monthlyInterest, tenure)) /
          (Math.pow(1 + monthlyInterest, tenure) - 1);

        this.input_data.EMI = emi.toFixed(2);
      } else {
        this.input_data.EMI = -1;
      }
      return this.input_data.EMI;
    },
    resetInputs: function () {
      this.input_data.Principal = 100000;
      this.input_data.Tenure = 6;
      this.input_data.RateOfInterest = 12;
      this.calculateEMI();
    },
  });
</script>
```

### Purpose of Changes

1. **Introducing Axios:** Axios is a widely-used

   JavaScript library that simplifies the process of sending asynchronous HTTP requests from the browser. By incorporating Axios into our frontend code, we can easily communicate with the FastAPI backend and retrieve data from the API endpoints.

1. **Updating Data Structure:**

   The existing frontend code uses property names like "Principal" and "RateOfInterest," which don't match the naming conventions commonly used in Python and FastAPI. To ensure consistency and improve readability, we will modify the property names to "principal," "rate_of_interest," and so on. This aligns the frontend code with the parameter names expected by the FastAPI backend.

### Libraries Used

1. **Axios:**

   Axios is a JavaScript library that simplifies the process of making HTTP requests from the browser. It provides an intuitive interface for sending GET requests to API endpoints, handling responses, and handling errors. By incorporating Axios, we can leverage its features to interact with the FastAPI backend seamlessly.

### Code Changes

```html
<script>
  const app = () => ({
    input_data: {
      principal: 100000,
      rate_of_interest: 12,
      tenure: 6,
      emi: 0.0,
    },
    output_data: { tpay: 0, instalments: [] },
    state: { activeButton: null },
    getData(endpoint) {
      axios
        .get(endpoint, { params: this.input_data })
        .then((response) => {
          this.input_data = response.data;
          this.output_data.tpay = (
            this.input_data.emi * this.input_data.tenure || 0
          ).toFixed(2);
        })
        .catch((error) => {
          console.error(error);
        });
    },
    resetInputs() {
      this.input_data.principal = 100000;
      this.input_data.tenure = 6;
      this.input_data.rate_of_interest = 12;
      this.getData("/calculate-emi");
    },
  });
</script>
```

#### Explanation

1. **The `getData`Function:**

   The `getData` function is added to handle the API call to the
   backend. It utilizes Axios to send a GET request to the specified `endpoint`, passing the `input_data` as parameters. Upon receiving the response from the backend, the data is assigned to the `input_data` object, and the `output_data.tpay` property is calculated based on the updated `input_data`.

1. **Updating the `resetInputs` Function:**

   The `resetInputs` function is updated to match the modified property names within the `input_data` object. Additionally, it now calls the `getData` function with the "/calculate-emi" endpoint after resetting the inputs. This ensures that the initial data is fetched from the backend API when the inputs are reset.

By incorporating these changes and utilizing Axios, the repurposed frontend code enables seamless communication between the frontend and the FastAPI backend, allowing the frontend to make API calls and receive responses asynchronously.

## Adding Additional Buttons

To add the additional buttons required for performing different calculations, follow these steps:

1. Locate the existing HTML code for the reset button inside the `<thead>` section:

   ```html
   <div class="reset-container">
     <button class="reset-button" x-on:click="resetInputs">Reset</button>
   </div>
   ```

1. Below the existing reset button code, add the following HTML code for the additional buttons:

   ```html
   <div class="reset-container">
     <button class="reset-button" x-on:click="getData('/calculate-emi')">
       Calculate EMI
     </button>
     <button class="reset-button" x-on:click="getData('/calculate-principal')">
       Calculate Principal
     </button>
     <button class="reset-button" x-on:click="getData('/calculate-rate')">
       Calculate Rate
     </button>
     <button class="reset-button" x-on:click="getData('/calculate-tenure')">
       Calculate Tenure
     </button>
     <button class="reset-button" x-on:click="resetInputs">Reset</button>
   </div>
   ```

   These new buttons have the `reset-button` class for styling purposes and use the `x-on:click` directive to bind the corresponding functions in the Alpine.js code.

By adding these additional buttons, you have enabled the ability to perform different calculations using the EMI calculator. Each button is associated with a specific API endpoint and triggers the corresponding function in the Alpine.js code when clicked. The API endpoint is passed as a parameter to the `getData` function, which will make a request to the backend API and update the input and output data accordingly.

## Setting Up the Application Directory Structure

To organize our application files, we will create a directory named "emic" and structure it as follows:

1. Create the "emic" directory:

   ```bash
   mkdir emic
   ```

2. Repurpose the existing `pyproject.toml` file:

   Copy the existing `pyproject.toml` file to the "emic" directory:

   ```bash
   cp pyproject.toml emic/
   ```

3. Create the "app" directory:

   ```bash
   mkdir emic/app
   ```

4. Copy the "main.py" file and make the necessary code changes:

   Copy the existing `main.py` file to the "emic/app" directory:

   ```bash
   cp main.py emic/app/
   ```

5. Create the "static" directory:

   ```bash
   mkdir emic/static
   ```

6. Copy the "index.html" file and make the necessary changes:

   Copy the existing `index.html` file to the "emic/static" directory:

   ```bash
   cp index.html emic/static/
   ```

After performing these steps, the application directory structure will look like this:

```bash
emic
├── app
│   └── main.py
├── pyproject.toml
└── static
    └── index.html
```

This directory structure ensures that the necessary files are organized in separate folders for better management of the application's backend, frontend, and static files.

## Running the Application

To run the application, follow these steps:

1. Open your terminal and navigate to the root directory of the "emic" application.

2. Execute the following command to start the application server:

   ```bash
   poetry run uvicorn app.main:app --reload
   ```

   This command uses Poetry to run the Uvicorn server and specifies the entry point of the application as `app.main:app`. The `--reload` flag enables automatic reloading of the server whenever code changes are made.

3. Once the server is running, open your web browser and visit [http://localhost:8000](http://localhost:8000). This will take you to the web page of the EMI calculator application.


![Update web app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/io1o8ixaoziovyjixjzl.png)

4. You can also access the API documentation by visiting [http://localhost:8000/docs](http://localhost:8000/docs). This page provides detailed information about the API endpoints and allows you to interact with them.

By following these steps, you will be able to run the EMI calculator application locally. The web page will display the calculator interface, and you can use it to perform EMI calculations. Additionally, the API documentation will provide you with the necessary details to understand and interact with the application's backend API.