Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 70

Building a Dynamic Two-Page App with API Data

Recommended resources

Hello! Over the past few lessons, you've acquired a powerful set of skills for building dynamic web applications with Vue. You've learned how to:

  • Install and configure Vue Router to create a Single-Page Application (SPA).
  • Define static and dynamic routes to map URLs to specific view components.
  • Navigate between these views using <RouterLink>.
  • Fetch data from an API using the onMounted lifecycle hook.
  • Professionally manage loading and error states to improve user experience.

In this lesson, we'll bring all of these concepts together to complete the mini-project for this module.

Your goal is to build a two-page application that fetches a list of items from an API and displays details for each item on a dynamic route page. This project will solidify your understanding of how to build a core, and very common, piece of functionality found in countless web applications.


Project Overview: The "VueMart" Product Catalog

We're going to build a simple e-commerce-style product catalog. The application will have two main pages:

  1. A Home Page (/): This page will fetch and display a list of all available products.
  2. A Product Detail Page (/product/:id): This is a dynamic route. When a user clicks on a product from the home page, they will be taken here to see more details about that specific product.

This project will require you to structure your routes, fetch data for both a list and a single item, and handle all the navigation and state management in between.


Part 1: Project Setup

Before we write any Vue code, let's get our project foundation and data source ready.

1. Data Source: json-server

Just as in our previous lessons, we'll use json-server to create a mock REST API.

  1. In your project directory, create a file named db.json.

  2. Paste the following product data into db.json:

    {
      "products": [
        {
          "id": 1,
          "title": "Ergonomic Office Chair",
          "description": "A comfortable and supportive chair designed for long hours of work. Features adjustable height, lumbar support, and breathable mesh back.",
          "price": 249.99
        },
        {
          "id": 2,
          "title": "Wireless Mechanical Keyboard",
          "description": "A compact 75% layout keyboard with tactile switches, RGB backlighting, and dual-mode connectivity (Bluetooth & USB-C).",
          "price": 119.00
        },
        {
          "id": 3,
          "title": "4K Ultra-HD Monitor",
          "description": "A 27-inch IPS monitor with 4K resolution, 99% sRGB color gamut, and thin bezels. Perfect for content creation and gaming.",
          "price": 429.50
        },
        {
          "id": 4,
          "title": "Noise-Cancelling Headphones",
          "description": "Over-ear headphones with industry-leading active noise cancellation, 30-hour battery life, and crystal-clear audio.",
          "price": 349.00
        }
      ]
    }
    
  3. Open a separate terminal window, navigate to your project folder, and run json-server to start the mock API:

    json-server --watch db.json --port 3001
    

    You can now access your products at http://localhost:3001/products.

2. Vue Router Configuration

Now, let's define the routes for our application. We need a route for the home page and a dynamic route for the product detail page.

Open src/router/index.js and configure your routes array. It should look like this:

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/product/:id', // The dynamic segment is ':id'
      name: 'ProductDetail',
      // Route-level code-splitting for better performance
      component: () => import('../views/ProductDetailView.vue')
    }
  ]
})

export default router

You will also need to create the ProductDetailView.vue file inside the src/views directory. You can leave it empty for now; we'll build it in Part 3.

Vue.js Project Directory Structure with Router and Views
This image shows a typical Vue project structure. Notice how our page-level components, `HomeView.vue` and `ProductDetailView.vue`, reside in the `src/views` directory, while our routing configuration is in `src/router/index.js`.


Part 2: Building the Home Page (Product List)

The HomeView.vue component will be responsible for fetching all products and displaying them as a list of links.

Your task is to implement src/views/HomeView.vue with the following logic:

  1. Fetch Data: In the <script setup> block, use ref to create reactive variables for products, loading, and error. Use the onMounted hook to fetch data from http://localhost:3001/products.
  2. Handle States: Implement the try...catch...finally pattern you learned in the last lesson to manage the loading and error states.
  3. Render the List:
    • In the template, use v-if="loading" to show a "Loading..." message.
    • Use v-else-if="error" to show an error message.
    • Use v-else-if="products" and a v-for loop to iterate over the products array.
  4. Link to Detail Page: For each product in the loop, render a <RouterLink>. The to prop of this link should be dynamically bound to the correct path, like '/product/' + product.id.

The video below is an excellent refresher on this entire process, from creating the dynamic route to linking to it from a list.

Vue Router 4 Dynamic Routes | Vue Router 4 tutorial

Watch this video by Tony Xhepa to see a complete walkthrough of fetching a list, creating dynamic links with RouterLink, and setting up the corresponding detail page. The example uses movies, but the concept is identical to our products.

Watch the entire video (about 11 minutes). Pay close attention to how he: Defines the dynamic route in the router file (00:25). Fetches the list of items in one component (similar to our HomeView). Uses <RouterLink> with a bound :to prop to create links like /movies/:id (09:53). Fetches a single item's data in the detail view using the route parameter (02:08).



Part 3: Building the Product Detail Page

This is where the magic of dynamic routing happens. ProductDetailView.vue will render the details of any product based on the ID in the URL.

Your task is to implement src/views/ProductDetailView.vue:

  1. Get Route Parameter: Use the useRoute composable from vue-router to get access to the current route's details. The product ID will be available at route.params.id.
  2. Fetch Single Item: Just like on the home page, set up reactive refs for product, loading, and error. In the onMounted hook, use the id from the route parameters to construct your fetch URL (e.g., `http://localhost:3001/products/${route.params.id}`).
  3. Handle States: Again, use a try...catch...finally block for robust loading and error handling. This is especially important here, as a user could manually type in an invalid ID (e.g., /product/999), which would cause a 404 error from json-server. Your app should handle this gracefully.
  4. Render Details: In your template, use v-if/v-else-if to show the loading, error, or product data. If the product exists, display its title, description, and price.

For a deeper look at dynamic routing and accessing parameters, this resource is very helpful.

Vue 3 Routing - Beginner to Advanced

This video from Laiture provides a deep dive into dynamic routing. It's a great supplement if you want to reinforce the 'why' behind this pattern.

Focus on the segment from 32:57 to 42:31. This part specifically explains why we use one dynamic component (CarView.vue in his example) instead of many static ones, and it shows exactly how to use the useRoute composable to access the dynamic :id parameter.

Accessing Vue Router Dynamic Parameters in DevTools
If you have Vue DevTools installed, you can inspect the router state and see the parsed `params` object for your dynamic route. This is extremely useful for debugging.


Project Checklist and Final Touches

Use this checklist to make sure you've completed all the requirements.

Core Functionality:

  • json-server is set up with product data and running.
  • router/index.js defines a root route (/) and a dynamic product route (/product/:id).
  • HomeView.vue fetches and displays the list of all products.
  • HomeView.vue shows a loading state during the fetch.
  • Each product in the list correctly links to its detail page (e.g., /product/2).
  • ProductDetailView.vue correctly extracts the id from the URL.
  • ProductDetailView.vue fetches and displays the correct single product's data.
  • ProductDetailView.vue shows a loading state during its fetch.
  • Error Handling: Both views gracefully handle API errors (you can test this by stopping json-server or navigating to an invalid ID like /product/999).

Best Practice (Bonus):

  • Add a "Back to Products" link on the ProductDetailView.vue page using <RouterLink to="/">.
  • Implement a "404 Not Found" page for routes that don't match anything, as shown in the LogRocket article or the Laiture video. This involves adding a "catch-all" route at the end of your routes array.

The ultimate guide to Vue Router

For a text-based guide on the entire process, including dynamic routing and handling 404s, this LogRocket article is an excellent reference.

Review the sections on 'Dynamic routing', 'Navigating between routes', and 'Handling 404 errors'. They provide clear code examples for the concepts you're implementing in this project.



Conclusion

Congratulations! By completing this mini-project, you have successfully built a core component of a modern web application: a multi-page experience that fetches and displays dynamic data from an API. You've demonstrated your ability to manage routing, handle asynchronous operations, and create a resilient user experience. The List -> Detail pattern is something you will build time and time again in your career.

Key Takeaways:

  • Vue Router is the backbone of SPAs: It allows you to create different "pages" within a single application.
  • Dynamic routes are powerful: They enable you to create flexible, data-driven pages without repeating code.
  • Data fetching and routing are intertwined: You often need to fetch data based on the current route, and useRoute is the key to that connection.
  • Robust state management is non-negotiable: A professional application must always provide feedback to the user, whether it's loading or encountering an error.

Our "VueMart" app is now fully functional, but it's not much to look at. In our next module, Styling Vue Applications with Tailwind, we'll dive into the world of utility-first CSS to rapidly build beautiful, responsive interfaces for the applications you now know how to create.