Project Development
Once the design was complete, Charlie led the team in starting to code. The authentication module went live first—no login, no access. Next came the product module—CRUD operations and ISR caching for millions of products. The shopping cart and order modules followed closely behind. Alice tested the customer flow, while Bob verified the administrative functions; each module underwent end-to-end testing.
1. What You'll Learn
- Authentication Module: OAuth2 + JWT + auth middleware + role-based permissions
- Product Module: Millions of CRUD operations + ISR caching + SEO + search and filtering
- Shopping Cart Module: Pinia Store + Composable + Server API + Inventory Validation
- Order Module: Prisma Transactions + State Machine + Stripe Payments
- Internationalization and Performance: i18n in Three Languages + @nuxt/image + Lighthouse Audit
2. A True Story About a Team
(1) Pain Point: Design Plans Can't Be Implemented
Charlie has a complete set of design documents, but the team doesn't know what to work on first and what to do next. Alice wrote the product pages, but the shopping cart doesn't have an API yet; Bob wrote the admin panel, but authentication isn't set up yet. The modules are waiting on each other, and progress has stalled.
(2) A Solution for Incremental Development by Module
Develop each module in order of dependency: Authentication → Products → Shopping Cart → Orders → Internationalization. Test each module immediately upon completion, without relying on modules that are not yet finished.
(3) Results: Steady Progress
Each module takes 3-5 days to complete, and all core features will be launched within two weeks, at which point Alice will be able to complete the entire shopping process.
3. Development Milestones
(1) Module Development Gantt Chart
gantt
title MegaShop Development Milestones
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. Authentication Module
(1) ▶ Example: JWT Tool + Registration/Login API
// 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 })
}
Output:
// Execution Successful
// 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. Product Module
(1) ▶ Example: Product CRUD API (Prisma + Cache)
// 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}` }
)
Output:
// Execution Successful
(2) ▶ Example: Product Details + SEO
<!-- 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}`)
// Dynamic SEO per product
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'
})
// JSON-LD structured data
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>
Output:
// Execution Successful
6. Shopping Cart Module
(1) ▶ Example: cartStore + useCart
// 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 }
})
Output:
// Execution Successful
(2) ▶ Example: Shopping Cart API (with Inventory Validation)
// server/api/cart/add.post.ts
export default defineEventHandler(async (event) => {
const userId = event.context.user?.id
const { productId, quantity = 1 } = await readBody(event)
// Check stock
const product = await prisma.product.findUnique({ where: { id: productId } })
if (!product?.inStock) throw createError({ statusCode: 400, message: 'Product out of stock' })
// Upsert cart item
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 }
})
Output:
// Execution Successful
7. Order Module
(1) ▶ Example: Order Creation Transaction
// 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) => {
// Get cart items
const cartItems = await tx.cartItem.findMany({
where: { userId },
include: { product: true }
})
if (cartItems.length === 0) {
throw createError({ statusCode: 400, message: 'Cart is empty' })
}
// Check stock for all items
for (const item of cartItems) {
if (!item.product.inStock) {
throw createError({ statusCode: 400, message: `${item.product.name} out of stock` })
}
}
// Calculate total
const total = cartItems.reduce((sum, item) => sum + Number(item.product.price) * item.quantity, 0)
// Create order with items
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 } } }
})
// Clear cart
await tx.cartItem.deleteMany({ where: { userId } })
return newOrder
})
return { order, message: 'Order created' }
})
Output:
// Execution Successful
8. Internationalization and Performance
(1) ▶ Example: Complete production configuration in nuxt.config.ts
// 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 }]
})
Output:
// Execution Successful
❓ FAQ
shared/types.ts. The frontend and backend share these type definitions, and consistency is checked at compile time.prisma.findMany(); the route and response formats remain the same.📖 Summary
- Develop in order of dependency: Authentication → Products → Shopping Cart → Orders → Internationalization
- Authentication Module: JWT dual tokens + httpOnly cookie + auth middleware + admin role check
- Product Module: Prisma CRUD + Nitro Caching + ISR + SEO meta tags + JSON-LD
- Shopping Cart: Pinia Store + useCart Composable + Inventory Validation
- Order: Prisma Transaction Guarantees Atomicity + Stripe Payment Integration
📝 Exercises
- Basic Exercise (Difficulty: ⭐): Implement an authentication module (registration/login/refresh token), and verify that the cookie is set correctly
- Advanced Exercise (Difficulty: ⭐⭐): Implement the complete product CRUD workflow, including the shopping cart and checkout, as a fully functional end-to-end application.
- Challenge (Difficulty: ⭐⭐⭐): Complete all core modules of MegaShop + i18n + performance optimization, achieving a Lighthouse score of 90+
---|



