Part 4 - A Frontend for the RESTful API with Nuxt 3 and Pinia

Part 4 - A Frontend for the RESTful API with Nuxt 3 and Pinia

Welcome to part four of our RESTful API series! So far, we’ve built a robust backend with Node.js, Express, and PostgreSQL, complete with AI-enhanced product descriptions using OpenAI. But an API is only half the story. Today, we’re turning our attention to the frontend, where we’ll use the power of Nuxt 3 and Pinia to create a beautiful and reactive user interface. Imagine your marketplace coming to life, where products are elegantly displayed and state management is a breeze.

Grab your coding cape, because we’re about to embark on an adventure into the world of Nuxt 3.


Step 1: Setting Up a New Nuxt 3 Project

Let’s kick things off by creating a fresh Nuxt 3 project. Open your terminal and run:

npx nuxi init nuxt-frontend
cd nuxt-frontend
npm install

This sets up a basic Nuxt 3 project. Now, let’s install the dependencies we’ll need, including Pinia for state management:

npm install @pinia/nuxt

Next, open your nuxt.config.ts file and add Pinia to your Nuxt modules:

export default defineNuxtConfig({
    modules: ['@pinia/nuxt'],
});

Now, you’re ready to harness the power of Nuxt 3 and Pinia!


2220+ FREE RESOURCES FOR DEVELOPERS!! ❤️😍🥳 (updated daily)
1400+ Free HTML Templates
338+ Free News Articles
64+ Free AI Prompts
303+ Free Code Libraries
51+ Free Code Snippets & Boilerplates for Node, Nuxt, Vue, and more!
25+ Free Open Source Icon Libraries
Visit dailysandbox.pro for free access to a treasure trove of resources!

Step 2: Setting Up Pinia for State Management

Create a new folder named stores in the ~/ directory of your Nuxt project. Inside, create a file called products.js:

import { defineStore } from 'pinia';

export const useProductsStore = defineStore('products', {
    state: () => ({
        products: [],
    }),
    actions: {
        async fetchProducts() {
            try {
                const { data } = await useFetch('/api/products');
                this.products = data.value;
            } catch (error) {
                console.error('Error fetching products:', error);
            }
        },
    },
});

This Pinia store handles fetching product data from our backend API and storing it in the products array. The useFetch() function in Nuxt simplifies API calls, automatically handling reactivity and caching.


Step 3: Creating the Index Page to Display Products

Navigate to your pages directory and open index.vue. Here, we’ll set up a page that fetches and displays our products:

<script setup>
import { useProductsStore } from '~/stores/products';

const productsStore = useProductsStore();
const { products, fetchProducts } = productsStore;

onMounted(() => {
    fetchProducts();
});
</script>

<template>
  <div class="container">
    <h1>Our Products</h1>
    <div v-if="products.length === 0">Loading products...</div>
    <div v-else class="products-grid">
      <div v-for="product in products" :key="product.id" class="product-card">
        <h2>{{ product.name }}</h2>
        <p>{{ product.description }}</p>
        <p><strong>Price:</strong> ${{ product.price }}</p>
      </div>
    </div>
  </div>
</template>

<style scoped>
.container {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
}

.products-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 1.5rem;
}

.product-card {
    border: 1px solid #ddd;
    padding: 1rem;
    border-radius: 8px;
    background-color: #fff;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
</style>

Here’s a breakdown of what’s happening:

  • Template: We use v-for to iterate over the products array and display each product’s name, description, and price in a card layout.
  • Pinia Integration: We import and use the productsStore from Pinia, calling fetchProducts() when the component mounts to load our products from the backend.
  • Styling: A simple grid layout makes our product cards look neat and organized.

Step 4: Connecting the Frontend to Your API

By default, useFetch() in Nuxt will make requests relative to your app’s base URL. To point it to your backend API, you can configure the base URL in your nuxt.config.ts:

export default defineNuxtConfig({
    modules: ['@pinia/nuxt'],
    runtimeConfig: {
        public: {
            apiBase: process.env.API_BASE || 'http://localhost:3000',
        },
    },
});

Now, in your Pinia store, update the useFetch call to use this base URL:

const { data } = await useFetch(`${useRuntimeConfig().public.apiBase}/products`);

Step 5: Running Your Nuxt 3 Frontend

With everything set up, start your Nuxt 3 app:

npm run dev

Visit http://localhost:3000 in your browser, and you should see a beautifully rendered list of products, fetched dynamically from your backend. Congratulations! You’ve built a full-stack application, with a RESTful API and a sleek, modern frontend.


Why Nuxt 3 and Pinia Are a Game-Changer

  1. Seamless Integration: Nuxt 3’s powerful framework and Pinia’s intuitive state management work together like a well-oiled machine.
  2. Reactivity and Performance: useFetch() handles data reactivity, caching, and server-side rendering, making your app lightning-fast.
  3. Developer Experience: With its clean setup and helpful tooling, building with Nuxt 3 is a joy.

For more tips on web development, check out DailySandbox and sign up for our free newsletter to stay ahead of the curve!