Exercício Completar — Funcionalidades Essenciais
Na Fase 2, aprendemos sobre Pinia, Composable, Server API, Middleware e Plugins — agora é hora de juntar tudo. Os requisitos de Charlie são: o carrinho de compras deve ser totalmente integrado da API através da Store até a página; o painel de administração de Bob deve ter controles de acesso; e a experiência de compra de Alice deve ser fluida e ininterrupta.
1. O Que Você Vai Aprender
- Implementar uma API CRUD completa do carrinho de compras
- Store do Carrinho de Compras Pinia integrada com sincronização de estado SSR
- Desenvolver Composables customizados para
useCarteuseProductFilter - Configurar o middleware global
authpara proteger a página de checkout - Teste de integração ponta a ponta: API → Composable → Store → Renderização da página
2. Uma História Real Sobre uma Equipe
(1) Ponto Crítico: Módulos operam de forma independente, mas não conseguem se integrar
A página do carrinho de compras de Alice exibe produtos, mas os dados são perdidos após atualizar. A API de Bob está escrita, mas o frontend não a está chamando. O middleware foi escrito, mas não foi integrado. Cada módulo funciona bem por si só, mas quando colocados juntos, o sistema falha.
(2) Solução com Teste de Integração Ponta a Ponta
Integrar completamente a cadeia de dados da API → Composable → Store → Página, garantindo um fluxo claro de dados em cada etapa.
(3) Benefícios: Uma Experiência deCompra Completa
Alice pode adicionar itens ao carrinho → os itens são mantidos após atualizar → prosseguir para a página de checkout → o painel administrativo é protegido por controles de acesso; todo o processo é fluido.
3. Arquitetura Colaborativa dos Módulos Funcionais Essenciais da Fase 2
graph TB
A[MegaShop Fase 2] --> B[Camada Server API]
A --> C[Camada Composable]
A --> D[Camada de Estado - Pinia]
A --> E[Camada de Middleware]
A --> F[Camada de Plugin]
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 → Puro]
D --> D1[cartStore → items/total]
D --> D2[userStore → auth/profile]
E --> E1[auth.global.ts → Verificação de login]
E --> E2[admin.ts → Verificação de papel]
F --> F1[logger → Debug]
F --> F2[stripe.client → Pagamento]
C1 --> D1
C1 --> B2
C2 --> B1
(1) Caminho Completo do Fluxo de Dados
| Operação | Fluxo de Dados | Módulos Envolvidos |
|---|---|---|
| Navegar Produtos | API → useFetch → Página | Server API |
| Adicionar ao Carrinho | API → useCart → cartStore → Página | API + Composable + Store |
| Ver Carrinho | cartStore → Página | Store |
| Checkout | cartStore → useCheckout → Stripe → API | Store + Composable + Plugin + API |
| Painel Admin | auth middleware → admin middleware → página | Middleware |
4. API Completa do Carrinho de Compras
(1) ▶Exemplo: API Completa do Carrinho de Compras
// 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 }
})
Saída:
// Execução Bem-sucedida
// 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
})
// 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. Integração Pinia Store + Composable
(1) ▶Exemplo: cartStore Aprimorado
// 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 }
Saída:
// Execução Bem-sucedida
(2) ▶Exemplo: Composable useCart
// 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
}
}
Saída:
// Execução Bem-sucedida
6. Configuração do Middleware de Permissões
(1) ▶Exemplo: Uma Arquitetura Completa de Middleware
// 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)}`)
})
Saída:
// Execução Bem-sucedida
// 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. Teste de Integração Ponta a Ponta de Páginas
(1) ▶Exemplo: Página de Detalhes do Produto (Fluxo de Dados Completo)
<!-- 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>
Saída:
// Execução Bem-sucedida
8. Checklist de Teste de Integração
| Item de Teste | Método de Verificação | Resultado Esperado |
|---|---|---|
| API Disponível | curl /api/products | Retorna JSON de produtos |
| Compartilhamento de Estado da Store | Adicionar item na página inicial → Ver quantidade na página de detalhes | Quantidades correspondem |
| Chamada de Composable | Clicar em "Adicionar ao Carrinho" | Sucesso: Adicionado + Notificação |
| Proteção de Middleware | Acesso não autenticado a /admin | Redirecionar para /login |
| Plugin em execução | Ver console | Saída do Logger presente |
| Hidratação de Dados SSR | Ver Código Fonte | HTML Contém Dados do Produto |
| Persistência de Cookies | Atualizar Página | Dados do Carrinho Mantidos |
❓Perguntas Frequentes
addItem, use o valor retornado para atualizar o array items para garantir que a Store e o backend permaneçam sincronizados. Não apenas modifique a Store sem chamar a API.📖Resumo
- Fluxo de dados completo do carrinho de compras: API → useCart → cartStore → página; os dados permanecem consistentes em cada etapa
- A Pinia Store atualiza seu estado usando o valor retornado após chamar a API, garantindo sincronização entre cliente e servidor
- O Composable
useCartencapsula a Store, API e notificações; as páginas só precisam chamaraddToCart - Middleware auth global + middleware "admin" para implementar guarda de autorização de rotas completa
- Teste de integração ponta a ponta: Verifique cada componente — API, Store, Composable, Middleware e Plugin — passo a passo
📝Exercícios
- Exercício Básico (Dificuldade: ⭐): Complete a integração da API do carrinho de compras com o cartStore para que a contagem do carrinho seja atualizada quando o botão "Adicionar ao Carrinho" for clicado na página de detalhes do produto.
- Exercício Avançado (Dificuldade: ⭐⭐): Implemente um sistema completo de middleware: Se um usuário não autenticado acessar /checkout, redirecione para a página de login; se um usuário comum acessar /admin, retorne um erro 403.
- Desafio (Dificuldade: ⭐⭐⭐): Implemente um fluxo de compra ponta a ponta: Navegar produtos → Adicionar ao carrinho → Ver carrinho → Checkout (simular pagamento usando o plugin Stripe), garantindo consistência de dados durante todo o processo.
---|



