Featured resource
Forrester Wave Report 2025
Pluralsight named a Leader in the Forrester Wave™

Our tech skill development platform earned the highest scores possible across 11 criteria.

Learn more
  • Labs icon Lab
  • Core Tech
Labs

Guided: Product Search and Filtering in Full-stack Java with Spring Boot 3 and Vue

In today’s web applications, users expect fast and accurate search results. This Code Lab walks you through building a dynamic product search and filtering system in a full-stack Java app using Spring Boot 3 and Vue.js. You’ll create an interactive search feature for an online bookstore, allowing users to filter by category and price in real time. On the backend, you’ll use Spring Data JPA to write efficient, dynamic queries. On the frontend, Vue.js will power reactive components that provide instant feedback. You’ll also implement key optimizations like pagination and debounce to maintain performance with large datasets. In addition to functionality, the lab covers security best practices to ensure only authorized users access sensitive data. By the end, you’ll have a scalable search experience and the skills to apply these techniques in other full-stack Java applications.

Labs

Path Info

Level
Clock icon Beginner
Duration
Clock icon 44m
Published
Clock icon May 23, 2025

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Exploring the Initial Application

    Introduction

    Modern users expect fast, responsive search experiences when browsing products, but loading everything at once just won't scale. In this lab, you'll improve an online bookstore application built with Spring Boot 3 and Vue 3 by adding real-time search, filtering, and pagination features that keep the app fast and user-friendly even with large datasets.

    You'll begin with a simple full-stack app that displays a static product list. From there, you'll enhance the backend to support dynamic filtering, and update the frontend to make smart, reactive requests as users interact with the UI. Along the way, you'll also optimize performance with pagination and debounce techniques.

    This lab focuses on clean backend design, efficient API usage, and simple, reactive Vue components—no frontend caching, no advanced state management, and no complex filtering logic in the browser.

    By the end, you'll have a more powerful and scalable application—and a clear understanding of how to build practical search experiences in full-stack Java apps. ## Run the Application and Observe the Problem

    Before getting started, run the backend and frontend, confirm that the product list loads, and explore the limitations of the current implementation.

    Instructions

    1. In the Terminal, from the root folder of the project, run the backend using:

      mvn spring-boot:run
      
    2. In your browser, go to {{localhost:5173}}.

    3. Confirm that you can see a list of products loaded from the backend.

    Outcome

    You should now see a working, but basic product list in the browser. All products are fetched at once, and there is no way to:

    • Filter by category or price.
    • Search for product names or keywords.
    • Paginate the results to load them incrementally.

    This lab will guide you through implementing those features using Spring Boot and Vue.

  2. Challenge

    Add Filtering to the Backend API

    Add Filtering to the Backend API

    The current API returns the entire product list, no matter how many items there are or what the user is looking for. That approach doesn't scale and it doesn't give users control over what they see.

    In this step, you'll update the backend so that clients can request only the products they need. You'll modify the controller to accept query parameters for category, minimum price, and maximum price, and return just the matching results. This will give the frontend a clean, powerful way to ask for specific slices of data.

    Outcome

    You should see the values of category, minPrice, and maxPrice printed in the console. The API still returns all products, but it now accepts filter criteria ready for the next task. ### Outcome

    When you visit a URL like {{localhost:8090}}/api/products?category=Fiction&minPrice=10&maxPrice=30, the API should return only products in the "Fiction" category with prices between 10 and 30. If no parameters are passed, all products should be returned.

  3. Challenge

    Implement Pagination in the Backend API

    Implement Pagination in the Backend API

    Right now, every API request returns the full list of products even when there are hundreds or thousands. That's fine for a demo, but it doesn't scale.

    In this step, you'll add pagination support to the backend using Spring Data JPA's Pageable interface. Instead of returning all products, the API will return one “page” at a time based on page and size query parameters. This sets the foundation for a smoother user experience and significantly reduces load times as your dataset grows. The product list might start small, but it won't stay that way. Returning hundreds of records in a single response slows things down and wastes bandwidth, especially when users only want to see a few results at a time. ### Outcome

    The backend now returns a filtered and paginated list of products. The response includes metadata such as totalPages, totalElements, and number, confirming that pagination is active.

    The frontend can now request exactly the results the user is looking for—and only what’s needed. Even though your API is now paginated and working correctly, you might notice that your Vue frontend no longer shows any products.

    That's because the backend is now returning a paginated Page<Product> response. This is a wrapper object that includes both the actual product data and metadata like total pages and current page.

    Your Vue component is still trying to assign the entire response to the product list, but the actual products are now inside the content property of the response.

    To fix this, update your ProductList.vue component to assign products.value = response.data.content instead of assigning the entire response object:

    products.value = response.data.content
    

    This small change ensures that your frontend displays just the product array from the paginated response and gets things working again.

  4. Challenge

    Add Filtering UI in Vue

    Add Filtering UI in Vue

    Now that the backend supports filtering and pagination, it's time to connect the dots in the frontend. Right now, the Vue app just loads all products without giving the user any control over what they see.

    In this step, you'll add inputs for category, minimum price, and maximum price, and bind them to a reactive state. When the user changes a value, your Vue component will call the API with the corresponding query parameters fetching just the data that matches.

    This makes the app feel smarter and more responsive, even though all the logic still lives in the backend.

    Outcome

    When you type into the filter inputs, the bound values should update in real-time. The product list will not update yet, that happens in the next task. ### Outcome

    When you update any filter input, the product list updates automatically to show only matching results from the backend. Pagination is not handled yet—that comes next.

  5. Challenge

    Optimise Filtering with Debounce

    Optimize Filtering with Debounce

    Right now, your app sends a request every time a user types or adjusts a filter, even mid-keystroke. That leads to unnecessary API calls and a jittery user experience, especially on slower connections.

    In this step, you'll add debouncing to the filter logic. This ensures the app waits until the user has stopped typing before sending the request. The result: smoother interactions, fewer network calls, and a frontend that feels more intentional and responsive.

    The current filter implementation sends a request every time the user changes a value, even mid-keystroke. This can flood the backend with unnecessary calls, especially when users are typing quickly. ### Outcome

    Your product list now updates only after the user pauses input for 300ms. This reduces noise, avoids unnecessary network traffic, and makes the app feel more polished, all without external dependencies.

  6. Challenge

    Conclusion

    Conclusion

    You started with a static product list and transformed it into a dynamic, responsive search experience, all with clean separation between backend filtering and frontend display.

    Along the way, you:

    • Enhanced the Spring Boot backend to support filtering and pagination
    • Built a Vue interface that responds to user input in real time
    • Added debounce logic to optimize performance without relying on external libraries

    This pattern—combining efficient queries on the backend with minimal, reactive updates on the frontend—is a foundation you'll reuse again and again in real-world apps.

    Nice work!

Kevin is a Developer Educator, speaker and writer based in beautiful Northern Ireland. He specializes in web development across a range of languages and frameworks.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.