DEV Community

Cover image for Building an EMI Calculator Web Application with FastAPI and Alpine.js
abbazs
abbazs

Posted on • Updated on

Building an EMI Calculator Web Application with FastAPI and Alpine.js

In these tutorials, we have seen how to create an API for EMI calculator using FastAPI and how to create a standalone web page that can calculate EMI using Alpine.js. 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.

from fastapi import FastAPI
import numpy_financial as npf

app = FastAPI()
Enter fullscreen mode Exit fullscreen mode

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:

from fastapi import FastAPI
from starlette.responses import FileResponse
from fastapi.staticfiles import StaticFiles
import numpy_financial as npf
import numpy as np
Enter fullscreen mode Exit fullscreen mode

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.

app.mount("/static", StaticFiles(directory="static"), name="static")
Enter fullscreen mode Exit fullscreen mode

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.

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

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:

@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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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

<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>
Enter fullscreen mode Exit fullscreen mode

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

<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>
Enter fullscreen mode Exit fullscreen mode

Explanation

  1. The getDataFunction:

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:
   <div class="reset-container">
     <button class="reset-button" x-on:click="resetInputs">Reset</button>
   </div>
Enter fullscreen mode Exit fullscreen mode
  1. Below the existing reset button code, add the following HTML code for the additional buttons:
   <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>
Enter fullscreen mode Exit fullscreen mode

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:
   mkdir emic
Enter fullscreen mode Exit fullscreen mode
  1. Repurpose the existing pyproject.toml file:

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

   cp pyproject.toml emic/
Enter fullscreen mode Exit fullscreen mode
  1. Create the "app" directory:
   mkdir emic/app
Enter fullscreen mode Exit fullscreen mode
  1. Copy the "main.py" file and make the necessary code changes:

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

   cp main.py emic/app/
Enter fullscreen mode Exit fullscreen mode
  1. Create the "static" directory:
   mkdir emic/static
Enter fullscreen mode Exit fullscreen mode
  1. Copy the "index.html" file and make the necessary changes:

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

   cp index.html emic/static/
Enter fullscreen mode Exit fullscreen mode

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

emic
├── app
│   └── main.py
├── pyproject.toml
└── static
    └── index.html
Enter fullscreen mode Exit fullscreen mode

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:

   poetry run uvicorn app.main:app --reload
Enter fullscreen mode Exit fullscreen mode

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.

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

Update web app

  1. You can also access the API documentation by visiting 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.

Top comments (0)