Introduction to Client-Side Interactivity

Welcome to the third lesson in our course on building an image generation web application with Spring Boot! In our previous lessons, we built a solid foundation for our application by creating the HTML structure and styling it with CSS. We now have a visually appealing interface with tabs, form elements, and containers for displaying generated images.

However, our application is still static — clicking buttons does not do anything, and there is no way to actually generate images or switch between tabs. This is where JavaScript comes in. JavaScript is the programming language that brings web pages to life by adding interactivity and dynamic behavior.

In this lesson, we will focus on implementing the client-side functionality that will allow users to interact with our application. We will write JavaScript code to handle tab navigation, capture user input, send requests to our backend API, and display the generated images.

The JavaScript file we will be creating will serve as the bridge between our user interface and the Spring Boot backend. When a user submits a prompt, the frontend sends it to the Spring Boot application, which then calls the official Gemini API using the gemini-3.1-flash-image model to generate the image. The backend processes the Gemini response and returns a simplified result to the frontend for display. This handles all the client-side logic — from validating user input to making requests to the backend and updating the based on the responses.

Implementing Tab Navigation

Our application has two main tabs: "Generate Image" and "View History." We need to implement a function that will show the selected tab and hide the others when a user clicks on a tab button.

Let's create a file named script.js in the static/js folder of our Spring Boot application. This is the same folder where we placed our style.css file in the previous lesson.

Now, let's implement the openTab() function that will handle the tab switching:

This function does two main things:

  1. First, it selects all elements with the class .tab-content using document.querySelectorAll() and hides them by setting their display style property to 'none'.
  2. Then, it selects the specific tab content element with the ID matching the tabName parameter using and shows it by setting its style property to .
Building the Image Generation Function

The core functionality of our application is generating images based on user input. We need to implement a function that will capture the user's input, validate it, and send it to our Spring Boot backend — which in turn calls the Gemini API with gemini-3.1-flash-image to produce the image.

Let's add the generateImage() function to our script.js file:

Let's break down this function step by step.

Step 1: Capture User Input

First, we need to retrieve the values from the input fields where the user enters a prompt and selects an aspect ratio:

These values will be included in the request to our backend API.

Step 2: Get References to UI Elements

Next, we get references to the key elements in the UI that we will need to interact with:

These elements will be used to show a loading state, hide the button, and display the generated image.

Step 3: Validate Input

Before proceeding, we check that the user has entered a prompt:

This prevents the user from submitting an empty prompt and ensures meaningful input is sent to the backend.

Step 4: Update UI for Loading State

To indicate that the image is being generated, we update the UI accordingly:

This gives the user feedback and prepares the page to display a new image.

Step 5: Send Data to the Backend

We use the Fetch API to send the user's input to our Spring Boot backend:

This makes a POST request to the /api/generate_image endpoint, which is a route defined in our Spring Boot application — not a direct call to the Gemini API. The JSON body shown here (user_input, aspect_ratio) is the contract between our frontend and our own backend, not the Gemini API's native request format.

Once the Spring Boot controller receives this request, it internally builds a generateContent call to the Gemini API using the gemini-3.1-flash-image model, passing the user's prompt along with any image-generation parameters. The browser never communicates with Gemini directly; that interaction is entirely handled on the server side.

Step 6: Handle API Response

Once we receive a response from the backend, we parse the JSON and update the UI:

This code resets the UI, checks for errors, and displays the generated image if the request was successful. Note that the Spring Boot backend has already extracted and normalized the image data from the Gemini API response before returning it here — the data.image field is a plain base64 string that the frontend can use directly, rather than the raw Gemini response structure.

Step 7: Catch Errors

Finally, we handle any unexpected issues that may occur during the request:

This ensures the user is informed and the UI returns to a usable state if something goes wrong.

Now, we are ready to send the request to our backend API. We will explore this in more detail in the next section.

Working with the Fetch API

To send the user's input to our Spring Boot backend, we will use the Fetch API, which is a modern interface for making HTTP requests in JavaScript.

In our generateImage() function, we use fetch() to send a POST request to the /api/generate_image endpoint:

Let's break down this fetch request:

  • The first argument is the URL of the endpoint we are sending the request to: /api/generate_image. This is a route served by our local Spring Boot application.
  • The second argument is an object with options for the request:
    • method: 'POST' specifies that we are sending a POST request, which is appropriate for creating new resources (in this case, generating a new image).
Handling API Responses

After sending the request to our Spring Boot backend, we need to process the response and update our UI accordingly. This happens in the second .then() callback of our fetch operation:

First, we reset the UI to its normal state by hiding the loading message and showing the generate button:

Then, we check if the response contains an error message:

If there is an error, we display it to the user using an alert.

If there is no error, we assume the response contains the generated image as a base64-encoded string:

Here, we:

  1. Create a new img element using document.createElement('img').
  2. Set its src attribute to a data URL that includes the -encoded image data from the response. The format tells the browser that this is a image encoded in .
Summary and Next Steps

Congratulations! In this lesson, we have implemented the client-side functionality for our image generation web application. Let's review what we have accomplished:

  1. We created a JavaScript file to handle the client-side logic of our application.
  2. We implemented the openTab() function to allow users to switch between tabs.
  3. We built the generateImage() function to capture user input, validate it, and send it to our Spring Boot backend.
  4. We used the Fetch API to make asynchronous requests to our Spring Boot server — which internally calls the official Gemini API using gemini-3.1-flash-image to generate images.
  5. We processed the backend-normalized responses and updated the UI to display the generated images.

Our application now has a functional user interface that allows users to generate images based on their input. However, there is still room for improvement. In the next lesson, we will enhance our application by adding more robust loading states and error handling, as well as implementing the functionality to view previously generated images in the history tab.

In the practice exercises that follow this lesson, you will have the opportunity to experiment with the JavaScript code we have written. You might try modifying the validation logic, adding additional features, or improving the error handling. These exercises will help reinforce your understanding of the concepts we've covered and give you hands-on experience with client-side JavaScript development.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal