404 Not Found

404 Not Found


nginx

Project Development

Com o design completo, Charlie liderou a equipe começando a codificar. O módulo de autenticação entrou no ar primeiro—sem login, sem acesso. Depois veio o módulo de produto—operações CRUD e cache ISR para milhões de produtos. O carrinho de compras e os módulos de pedido seguiram de perto. Alice testou o fluxo do cliente, enquanto Bob verificou as funções administrativas; cada módulo passou por testes de ponta a ponta.

1. O Que Você Vai Aprender


2. Uma História Real Sobre uma Equipe

(1) Dor: Planos de Design Não Podem Ser Implementados

Charlie tem um conjunto completo de documentos de design, mas a equipe não sabe o que fazer primeiro e o que fazer depois. Alice escreveu as páginas de produto, mas o carrinho de compras ainda não tem API; Bob escreveu o painel admin, mas a autenticação ainda não está configurada. Os módulos dependem uns dos outros, e o progresso estagnou.

(2) Uma Solução para Desenvolvimento Incremental por Módulo

Desenvolva cada módulo na ordem de dependência: Autenticação → Produtos → Carrinho de Compras → Pedidos → Internacionalização. Teste cada módulo imediatamente após a conclusão, sem depender de módulos que ainda não estão prontos.

(3) Resultados: Progresso Constante

Cada módulo leva de 3 a 5 dias para ser concluído, e todos os recursos centrais serão lançados em duas semanas, momento em que Alice poderá completar todo o processo de compras.


3. Marcos de Desenvolvimento

(1) Gráfico de Gantt de Desenvolvimento de Módulos

100%
gantt
    title Marcos de Desenvolvimento do MegaShop
    dateFormat YYYY-MM-DD
    section Auth Module
    JWT + OAuth2 :a1, 2026-01-01, 3d
    Auth Middleware :a2, after a1, 2d
    section Product Module
    CRUD API :p1, after a2, 3d
    Search + Filter :p2, after p1, 2d
    ISR Cache :p3, after p2, 1d
    section Cart Module
    Store + API :c1, after a2, 3d
    Composable :c2, after c1, 1d
    section Order Module
    Prisma Transaction :o1, after c1, 3d
    Stripe Payment :o2, after o1, 2d
    section i18n + Perf
    Three Languages :i1, after p3, 2d
    Image Optimization :i2, after i1, 1d
    Lighthouse Audit :i3, after i2, 1d

4. Módulo de Autenticação

(1) ▶ Exemplo: Ferramenta JWT + API de Registro/Login

TYPESCRIPT
// server/utils/jwt.ts
import jwt from 'jsonwebtoken'

const config = useRuntimeConfig()

export function signTokenPair(payload: { userId: number; email: string; role: string }) {
  return {
    accessToken: jwt.sign(payload, config.jwtAccessSecret, { expiresIn: '15m' }),
    refreshToken: jwt.sign(payload, config.jwtRefreshSecret, { expiresIn: '7d' })
  }
}

export function setAuthCookies(event: any, tokens: { accessToken: string; refreshToken: string }) {
  setCookie(event, 'access-token', tokens.accessToken, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 900 })
  setCookie(event, 'refresh-token', tokens.refreshToken, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 604800 })
}

Saída:

TEXT
// Execução bem-sucedida
TYPESCRIPT
// server/api/auth/register.post.ts
export default defineEventHandler(async (event) => {
  const { email, password, name } = await readBody(event)
  const existing = await prisma.user.findUnique({ where: { email } })
  if (existing) throw createError({ statusCode: 409, message: 'Email already registered' })

  const hashedPassword = await hashPassword(password)
  const user = await prisma.user.create({
    data: { email, name, password: hashedPassword, role: 'CUSTOMER' }
  })

  const tokens = signTokenPair({ userId: user.id, email: user.email, role: user.role })
  setAuthCookies(event, tokens)

  return { user: { id: user.id, email: user.email, name: user.name, role: user.role } }
})

5. Módulo de Produto

(1) ▶ Exemplo: API CRUD de Produto (Prisma + Cache)

TYPESCRIPT
// server/api/products/index.get.ts
export default cachedEventHandler(
  async (event) => {
    const { page = '1', limit = '20', category, search, sort } = getQuery(event)

    const where = {
      AND: [
        category ? { category: { slug: category as string } } : {},
        search ? { OR: [
          { name: { contains: search as string, mode: 'insensitive' } },
          { description: { contains: search as string, mode: 'insensitive' } }
        ]} : {}
      ]
    }

    const orderBy = sort === 'price-asc' ? { price: 'asc' }
      : sort === 'price-desc' ? { price: 'desc' }
      : { createdAt: 'desc' }

    const [items, total] = await Promise.all([
      prisma.product.findMany({
        where, orderBy,
        skip: (Number(page) - 1) * Number(limit),
        take: Number(limit),
        include: { category: { select: { name: true, slug: true } } }
      }),
      prisma.product.count({ where })
    ])

    return { items, total, page: Number(page), limit: Number(limit) }
  },
  { maxAge: 60, swr: true, getKey: (event) => `products:${getRequestURL(event).search}` }
)

Saída:

TEXT
// Execução bem-sucedida

(2) ▶ Exemplo: Detalhes do Produto + SEO

VUE
<!-- pages/products/[id].vue -->
<template>
  <div v-if="product">
    <h1>{{ product.name }}</h1>
    <p>{{ formatFromUSD(product.price) }}</p>
    <button @click="handleAddToCart">{{ t('product.addToCart') }}</button>
  </div>
</template>

<script setup lang="ts">
const route = useRoute()
const config = useRuntimeConfig()
const { t } = useI18n()
const { formatFromUSD } = useLocalizedPrice()
const { addToCart } = useCart()

const { data: product } = await useFetch(`/api/products/${route.params.id}`)

// SEO dinâmico por produto
useSeoMeta({
  title: () => `${product.value?.name} - MegaShop`,
  description: () => `Buy ${product.value?.name} - $${product.value?.price} USD`,
  ogTitle: () => product.value?.name,
  ogImage: () => product.value?.image,
  ogType: 'product'
})

// Dados estruturados JSON-LD
useHead({ script: [{ type: 'application/ld+json', innerHTML: computed(() => JSON.stringify({
  '@context': 'https://schema.org', '@type': 'Product',
  name: product.value?.name,
  offers: { '@type': 'Offer', priceCurrency: 'USD', price: product.value?.price }
}))}]})

async function handleAddToCart() {
  if (product.value) await addToCart(product.value.id, product.value.name)
}
</script>

Saída:

TEXT
// Execução bem-sucedida

6. Módulo de Carrinho de Compras

(1) ▶ Exemplo: cartStore + useCart

TYPESCRIPT
// stores/cart.ts
export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])
  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))

  async function addItem(productId: number) {
    const cart = await $fetch('/api/cart/add', { method: 'POST', body: { productId } })
    items.value = cart.items
  }
  async function removeItem(productId: number) {
    const cart = await $fetch('/api/cart/remove', { method: 'DELETE', body: { productId } })
    items.value = cart.items
  }
  async function fetchCart() {
    const cart = await $fetch('/api/cart')
    items.value = cart.items || []
  }

  return { items, totalItems, totalPrice, addItem, removeItem, fetchCart }
})

Saída:

TEXT
// Execução bem-sucedida

(2) ▶ Exemplo: API do Carrinho de Compras (com Validação de Estoque)

TYPESCRIPT
// server/api/cart/add.post.ts
export default defineEventHandler(async (event) => {
  const userId = event.context.user?.id
  const { productId, quantity = 1 } = await readBody(event)

  // Verificar estoque
  const product = await prisma.product.findUnique({ where: { id: productId } })
  if (!product?.inStock) throw createError({ statusCode: 400, message: 'Product out of stock' })

  // Upsert item do carrinho
  await prisma.cartItem.upsert({
    where: { userId_productId: { userId, productId } },
    update: { quantity: { increment: quantity } },
    create: { userId, productId, quantity }
  })

  const cart = await prisma.cartItem.findMany({
    where: { userId },
    include: { product: { select: { name: true, price: true, image: true } } }
  })

  const total = cart.reduce((s, i) => s + Number(i.product.price) * i.quantity, 0)
  return { items: cart, total }
})

Saída:

TEXT
// Execução bem-sucedida

7. Módulo de Pedido

(1) ▶ Exemplo: Transação de Criação de Pedido

TYPESCRIPT
// server/api/orders/index.post.ts
export default defineEventHandler(async (event) => {
  const userId = event.context.user?.id
  if (!userId) throw createError({ statusCode: 401, message: 'Login required' })

  const order = await prisma.$transaction(async (tx) => {
    // Obter itens do carrinho
    const cartItems = await tx.cartItem.findMany({
      where: { userId },
      include: { product: true }
    })

    if (cartItems.length === 0) {
      throw createError({ statusCode: 400, message: 'Cart is empty' })
    }

    // Verificar estoque de todos os itens
    for (const item of cartItems) {
      if (!item.product.inStock) {
        throw createError({ statusCode: 400, message: `${item.product.name} out of stock` })
      }
    }

    // Calcular total
    const total = cartItems.reduce((sum, item) => sum + Number(item.product.price) * item.quantity, 0)

    // Criar pedido com itens
    const newOrder = await tx.order.create({
      data: {
        userId,
        total,
        status: 'PENDING',
        items: {
          create: cartItems.map(item => ({
            productId: item.productId,
            quantity: item.quantity,
            price: item.product.price
          }))
        }
      },
      include: { items: { include: { product: true } } }
    })

    // Limpar carrinho
    await tx.cartItem.deleteMany({ where: { userId } })

    return newOrder
  })

  return { order, message: 'Order created' }
})

Saída:

TEXT
// Execução bem-sucedida

8. Internacionalização e Performance

(1) ▶ Exemplo: Configuração completa de produção no nuxt.config.ts

TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
  ssr: true,

  modules: [
    '@pinia/nuxt',
    '@nuxtjs/tailwindcss',
    '@nuxtjs/i18n',
    '@nuxtjs/sitemap',
    '@nuxt/image',
    '~/modules/analytics'
  ],

  app: {
    head: {
      title: 'MegaShop',
      titleTemplate: '%s | MegaShop',
      meta: [
        { name: 'description', content: 'Over 1 million products shipped worldwide' }
      ]
    }
  },

  i18n: {
    locales: [
      { code: 'en', name: 'English', file: 'en.json', currency: 'USD' },
      { code: 'zh', name: 'Chinese', file: 'zh.json', currency: 'CNY' },
      { code: 'ja', name: 'Japanese', file: 'ja.json', currency: 'JPY' }
    ],
    defaultLocale: 'en',
    lazy: true,
    langDir: 'locales/',
    strategy: 'prefix_except_default'
  },

  image: {
    quality: 80,
    format: ['webp', 'avif']
  },

  sitemap: {
    hostname: 'https://megashop.com',
    gzip: true
  },

  runtimeConfig: {
    databaseUrl: process.env.DATABASE_URL,
    redisUrl: process.env.REDIS_URL,
    jwtAccessSecret: process.env.JWT_ACCESS_SECRET,
    jwtRefreshSecret: process.env.JWT_REFRESH_SECRET,
    public: {
      apiBase: process.env.API_BASE || '/api',
      siteUrl: process.env.SITE_URL || 'https://megashop.com',
      stripePublishableKey: process.env.STRIPE_PUBLISHABLE_KEY
    }
  },

  routeRules: {
    '/': { prerender: true },
    '/about': { prerender: true },
    '/products': { swr: 3600 },
    '/products/**': { swr: 86400 },
    '/categories/**': { swr: 21600 },
    '/admin/**': { ssr: false },
    '/cart': { ssr: false },
    '/checkout': { ssr: false },
    '/profile/**': { ssr: false },
    '/api/**': { cors: true },
    '/_nuxt/**': { headers: { 'cache-control': 'public, max-age=31536000, immutable' } }
  },

  nitro: {
    preset: process.env.DEPLOY_TARGET || 'node-server',
    storage: {
      products: { driver: process.env.NODE_ENV === 'production' ? 'redis' : 'memory' }
    }
  },

  components: [{ path: '~/components', pathPrefix: false }]
})

Saída:

TEXT
// Execução bem-sucedida

❓ Perguntas Frequentes

P A ordem em que os módulos são desenvolvidos é importante?
R É muito importante. A autenticação deve ser implementada primeiro (pois outros módulos dependem do estado logado), seguida por produtos (pois o carrinho e pedidos dependem dos dados de produto), e finalmente internacionalização (pois não afeta a funcionalidade central).
P Quanto código cada módulo requer, aproximadamente?
R Módulo de autenticação ~500 linhas, Módulo de produto ~800 linhas, Carrinho de compras ~400 linhas, Pedidos ~500 linhas, Internacionalização ~200 linhas. Incluindo configuração e testes, o total é aproximadamente 3.000-4.000 linhas.
P Como garantir contratos de API entre módulos?
R Use interfaces TypeScript para definir tipos de requisição e resposta da API, e coloque-as em shared/types.ts. O frontend e backend compartilham essas definições de tipos, e a consistência é verificada em tempo de compilação.
P Como transicionar de dados mock para Prisma durante o desenvolvimento?
R Mantenha o formato de interface da API inalterado; apenas substitua a implementação interna. Troque de um array mock para prisma.findMany(); a rota e os formatos de resposta permanecem os mesmos.
P Como testar pagamentos Stripe?
R Use uma chave de teste Stripe e um número de cartão de teste (4242 4242 4242 4242). Nenhuma cobrança real será processada. Troque para a chave de produção antes de ir ao ar.
P Quando a tradução i18n deve ser feita?
R i18n deve ser feita após os recursos centrais estarem completos. Primeiro, hard-code o texto em inglês; uma vez que os recursos estejam estáveis, extraia-os como chaves de tradução. Começar i18n cedo demais aumentará a complexidade do desenvolvimento.

📖 Resumo


📝 Exercícios

  1. Exercício Básico (Dificuldade: ⭐): Implemente um módulo de autenticação (registro/login/refresh token) e verifique que o cookie está configurado corretamente
  2. Exercício Avançado (Dificuldade: ⭐⭐): Implemente o fluxo CRUD completo de produto, incluindo carrinho de compras e checkout, como uma aplicação funcional de ponta a ponta.
  3. Desafio (Dificuldade: ⭐⭐⭐): Complete todos os módulos centrais do MegaShop + i18n + otimização de performance, alcançando uma pontuação Lighthouse de 90+

---|

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%