404 Not Found

404 Not Found


nginx

Comprehensive Exercise — Core Features

In Phase 2, we learned about Pinia, Composable, Server API, Middleware, and Plugins—now it's time to tie them all together. Charlie's requirements are: the shopping cart must be fully integrated from the API through the Store to the page; Bob's admin dashboard must have access controls; and Alice's shopping experience must be seamless and uninterrupted.

1. What You'll Learn


2. A True Story About a Team

(1) Pain Point: Modules operate independently but cannot be integrated

Alice's shopping cart page displays products, but the data is lost after refreshing. Bob's API is written, but the front end isn't calling it. The middleware is written but hasn't been integrated. Each module works fine on its own, but when they're put together, the system breaks down.

(2) Solutions for End-to-End Integration Testing

Fully integrate the data chain from API → Composable → Store → Page, ensuring a clear flow of data at every stage.

(3) Benefits: A Complete Shopping Experience

Alice can add items to the shopping cart → the items are retained after refreshing → proceed to the checkout page → the admin panel is protected by access controls; the entire process is seamless.


3. Collaborative Architecture of Phase 2 Core Functional Modules

100%
graph TB
    A[MegaShop Phase 2] --> B[Server API Layer]
    A --> C[Composable Layer]
    A --> D[State Layer - Pinia]
    A --> E[Middleware Layer]
    A --> F[Plugin Layer]

    B --> B1[/api/products CRUD]
    B --> B2[/api/cart CRUD]
    B --> B3[/api/auth Login/Register]

    C --> C1[useCart → Store + API]
    C --> C2[useProductFilter → API]
    C --> C3[usePriceFormat → Pure]

    D --> D1[cartStore → items/total]
    D --> D2[userStore → auth/profile]

    E --> E1[auth.global.ts → Login check]
    E --> E2[admin.ts → Role check]

    F --> F1[logger → Debug]
    F --> F2[stripe.client → Payment]

    C1 --> D1
    C1 --> B2
    C2 --> B1

(1) Complete Data Flow Path

Operation Data Flow Modules Involved
Browse Products API → useFetch → Page Server API
Add to Cart API → useCart → cartStore → Page API + Composable + Store
View Cart cartStore → Page Store
Checkout cartStore → useCheckout → Stripe → API Store + Composable + Plugin + API
Admin Dashboard auth middleware → admin middleware → page Middleware

4. Complete Shopping Cart API

(1) ▶ Example: Complete Shopping Cart API

TYPESCRIPT
// server/api/cart/index.get.ts
export default defineEventHandler((event) => {
  const sessionId = getCookie(event, 'session-id') || 'default'
  const cart = mockCarts.find(c => c.sessionId === sessionId)
  return cart || { sessionId, items: [], total: 0 }
})

Output:

TEXT
// Execution Successful
TYPESCRIPT
// server/api/cart/add.post.ts
export default defineEventHandler(async (event) => {
  const sessionId = getCookie(event, 'session-id') || 'default'
  const { productId, quantity = 1 } = await readBody(event)

  const product = mockProducts.find(p => p.id === productId)
  if (!product) throw createError({ statusCode: 404, message: 'Product not found' })

  let cart = mockCarts.find(c => c.sessionId === sessionId)
  if (!cart) {
    cart = { sessionId, items: [], total: 0 }
    mockCarts.push(cart)
  }

  const existing = cart.items.find(i => i.productId === productId)
  if (existing) {
    existing.quantity += quantity
  } else {
    cart.items.push({ productId, name: product.name, price: product.price, quantity, image: product.image })
  }
  cart.total = cart.items.reduce((s, i) => s + i.price * i.quantity, 0)
  setCookie(event, 'session-id', sessionId, { httpOnly: true, maxAge: 86400 * 30 })
  return cart
})
TYPESCRIPT
// server/api/cart/remove.delete.ts
export default defineEventHandler(async (event) => {
  const sessionId = getCookie(event, 'session-id') || 'default'
  const { productId } = await readBody(event)

  const cart = mockCarts.find(c => c.sessionId === sessionId)
  if (!cart) throw createError({ statusCode: 404, message: 'Cart not found' })

  cart.items = cart.items.filter(i => i.productId !== productId)
  cart.total = cart.items.reduce((s, i) => s + i.price * i.quantity, 0)
  return cart
})

5. Pinia Store + Composable Integration

(1) ▶ Example: Enhanced cartStore

TYPESCRIPT
// stores/cart.ts
export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])
  const isLoading = ref(false)

  const totalItems = computed(() => items.value.reduce((s, i) => s + i.quantity, 0))
  const totalPrice = computed(() => items.value.reduce((s, i) => s + i.price * i.quantity, 0))
  const formattedTotal = computed(() =>
    new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(totalPrice.value)
  )

  async function fetchCart() {
    isLoading.value = true
    try {
      const cart = await $fetch('/api/cart')
      items.value = cart.items || []
    } finally {
      isLoading.value = false
    }
  }

  async function addItem(productId: number, quantity = 1) {
    const cart = await $fetch('/api/cart/add', {
      method: 'POST', body: { productId, quantity }
    })
    items.value = cart.items
  }

  async function removeItem(productId: number) {
    const cart = await $fetch('/api/cart/remove', {
      method: 'DELETE', body: { productId }
    })
    items.value = cart.items
  }

  function clearCart() {
    items.value = []
  }

  return { items, isLoading, totalItems, totalPrice, formattedTotal, fetchCart, addItem, removeItem, clearCart }
})

interface CartItem { productId: number; name: string; price: number; quantity: number; image: string }

Output:

TEXT
// Execution Successful

(2) ▶ Example: useCart Composable

TYPESCRIPT
// composables/useCart.ts
export function useCart() {
  const cartStore = useCartStore()
  const { $logger } = useNuxtApp()
  const { notify } = useNotification()

  async function addToCart(productId: number, productName: string) {
    try {
      await cartStore.addItem(productId)
      notify(`${productName} added to cart`, 'success')
      $logger.info('Item added to cart', { productId })
    } catch (err) {
      notify('Failed to add item', 'error')
      $logger.error('Add to cart failed', { productId, err })
    }
  }

  async function removeFromCart(productId: number) {
    try {
      await cartStore.removeItem(productId)
      notify('Item removed from cart', 'info')
    } catch (err) {
      notify('Failed to remove item', 'error')
    }
  }

  return {
    items: computed(() => cartStore.items),
    totalItems: computed(() => cartStore.totalItems),
    totalPrice: computed(() => cartStore.totalPrice),
    formattedTotal: computed(() => cartStore.formattedTotal),
    isLoading: computed(() => cartStore.isLoading),
    addToCart,
    removeFromCart
  }
}

Output:

TEXT
// Execution Successful

6. Permissions Middleware Configuration

(1) ▶ Example: A Complete Middleware Architecture

TYPESCRIPT
// middleware/01-auth.global.ts
export default defineNuxtRouteMiddleware((to) => {
  const publicPaths = ['/', '/products', '/about', '/login', '/register']
  if (publicPaths.some(p => to.path === p || to.path.startsWith('/products'))) return

  const token = useCookie('auth-token')
  if (!token.value) return navigateTo(`/login?redirect=${encodeURIComponent(to.path)}`)
})

Output:

TEXT
// Execution Successful
TYPESCRIPT
// middleware/admin.ts
export default defineNuxtRouteMiddleware((to) => {
  const userCookie = useCookie('user-data')
  if (!userCookie.value) return navigateTo('/login')
  if ((userCookie.value as any).role !== 'admin') {
    throw createError({ statusCode: 403, message: 'Admin access required' })
  }
})

7. End-to-End Integration Testing of Pages

(1) ▶ Example: Product Detail Page (Complete Data Flow)

VUE
<!-- pages/products/[id].vue -->
<template>
  <div v-if="product" class="product-detail">
    <img :src="product.image" :alt="product.name" />
    <div class="info">
      <h1>{{ product.name }}</h1>
      <p class="price">{{ formattedPrice }}</p>
      <p>{{ product.reviewCount }} reviews</p>
      <button @click="handleAddToCart" :disabled="adding">
        {{ adding ? 'Adding...' : 'Add to Cart' }}
      </button>
    </div>
  </div>
</template>

<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
const { formatted } = usePriceFormat(computed(() => product.value?.price || 0))
const formattedPrice = formatted

const { addToCart } = useCart()
const adding = ref(false)

async function handleAddToCart() {
  if (!product.value) return
  adding.value = true
  await addToCart(product.value.id, product.value.name)
  adding.value = false
}
</script>

Output:

TEXT
// Execution Successful

8. Integration Testing Checklist

Test Item Verification Method Expected Result
API Available curl /api/products Returns product JSON
Store Status Sharing Add item on the home page → View quantity on the details page Quantities match
Composable Call Click "Add to Cart" Success: Added + Notification
Middleware Protection Unauthenticated access to /admin Redirect to /login
Plugin is running View console Logger output present
SSR Data Hydration View Source Code HTML Contains Product Data
Cookie Persistence Refresh Page Shopping Cart Data Retention

❓ FAQ

Q What should I do if the Store and API data are out of sync?
A After the Store calls the API via addItem, use the return value to update the items array to ensure the Store and the backend remain in sync. Do not just modify the Store without calling the API.
Q Is it redundant to use both Store and $fetch in Composable?
A No. Store manages client-side state, while $fetch synchronizes with the backend. useCart wraps both together, so the caller doesn't need to worry about the details.
Q Both middleware and API middleware handle authentication. Will this cause a conflict?
A No. Page middleware protects route transitions (front end), while server middleware protects API requests (back end). The two are complementary and both are necessary.
Q How do I debug the end-to-end data flow?
A Use the Pinia panel in Nuxt DevTools to view the Store state, the Network panel to view API requests, and the console to view logger output. Troubleshoot step by step, starting from the API response → Store update → page rendering.
Q How is the shopping cart data initialized during SSR?
A During SSR, the session ID is retrieved via a cookie, and the API returns the shopping cart based on the session ID. After the client-side hydration is complete, the Store retrieves the data from the API payload.
Q When will the mock data in Phase 2 be replaced with the real database?
A In Lesson 20 of Phase 4, we'll replace the mocks with Prisma. The API interface format has already been designed; all that's left is to modify the internal implementation of the server API.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty: ⭐): Complete the integration of the shopping cart API with cartStore so that the shopping cart count updates when the "Add to Cart" button is clicked on the product details page.
  2. Advanced Problem (Difficulty: ⭐⭐): Implement a complete middleware system: If an unauthenticated user accesses /checkout, redirect them to the login page; if a regular user accesses /admin, return a 403 error.
  3. Challenge (Difficulty: ⭐⭐⭐): Implement an end-to-end shopping process: Browse products → Add to cart → View cart → Checkout (simulate payment using the Stripe plugin), ensuring data consistency throughout the entire process.

---|

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏