Development Guide
Frontend Development
Technology Stack
- Vue 3 Composition API - Write components using modern Composition API
- TypeScript - Strict mode ensuring type safety
- Element Plus - UI component library based on Material Design
- Tailwind CSS - Atomic CSS framework
- Nuxt 4 - Full-stack framework providing SSR and API capabilities
Component Development
Creating Components
vue
<!-- app/components/MyComponent.vue -->
<script setup lang="ts">
// Using TypeScript types
interface Props {
title: string
count?: number
}
// Define Props
const props = withDefaults(defineProps<Props>(), {
count: 0
})
// Define Emits
const emit = defineEmits<{
update: [value: number]
delete: []
}>()
// Reactive data
const message = ref('Hello World')
const items = ref<string[]>([])
// Computed properties
const doubledCount = computed(() => props.count * 2)
// Methods
const handleClick = () => {
emit('update', props.count + 1)
}
// Lifecycle
onMounted(() => {
console.log('Component mounted')
})
</script>
<template>
<div class="my-component">
<h1>{{ title }}</h1>
<p>{{ message }}</p>
<el-button @click="handleClick">
Count: {{ count }}
</el-button>
</div>
</template>
<style scoped lang="css">
.my-component {
/* Styles */
}
</style>Using Composable Functions
typescript
// app/composables/useCounter.ts
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubledCount = computed(() => count.value * 2)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return {
count,
doubledCount,
increment,
decrement,
reset
}
}vue
<!-- Using in components -->
<script setup lang="ts">
const { count, increment, decrement, reset } = useCounter(0)
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
</div>
</template>API Request Wrapper
typescript
// app/composables/useApi.ts
export const useApi = () => {
const config = useRuntimeConfig()
const request = async <T>(url: string, options?: RequestInit): Promise<T> => {
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
})
const data = await response.json()
if (!response.ok || !data.success) {
throw new Error(data.message || 'Request failed')
}
return data.data
} catch (error) {
console.error('API request error:', error)
throw error
}
}
return { request }
}Page Development
vue
<!-- app/pages/articles/[slug].vue -->
<script setup lang="ts">
// Get route parameters
const route = useRoute()
const slug = route.params.slug
// Get article data
const { data: article, pending, error } = await useFetch(`/api/articles/${slug}`)
// SEO optimization
useSeoMeta({
title: article.value?.title || 'Article Details',
description: article.value?.summary,
ogTitle: article.value?.title,
ogImage: article.value?.cover_image
})
</script>
<template>
<div>
<div v-if="pending">Loading...</div>
<div v-else-if="error">Failed to load</div>
<article v-else-if="article">
<h1>{{ article.title }}</h1>
<p>{{ article.summary }}</p>
<div v-html="article.content"></div>
</article>
</div>
</template>Backend Development
API Route Development
Nuxt 3 uses file system routing, API files are placed in server/api/ directory.
typescript
// server/api/hello.get.ts
export default defineEventHandler(async (event) => {
return {
success: true,
message: 'Hello World'
}
})API with Parameters
typescript
// server/api/articles/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
// Get article
const article = await ArticleModel.findById(id)
if (!article) {
throw createError({
statusCode: 404,
statusMessage: 'Article not found'
})
}
return {
success: true,
data: article
}
})POST Request
typescript
// server/api/articles/create.post.ts
export default defineEventHandler(async (event) => {
try {
// Read request body
const body = await readBody(event)
// Get current user (requires authentication middleware)
const user = event.context.user
// Parameter validation
if (!body.title || !body.content) {
return {
success: false,
message: 'Title and content cannot be empty'
}
}
// Create article
const article = await ArticleModel.create({
...body,
author_id: user.userId
})
return {
success: true,
data: article,
message: 'Created successfully'
}
} catch (error) {
console.error('Failed to create article:', error)
return {
success: false,
message: 'Creation failed'
}
}
})Middleware Development
typescript
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
// Skip login related paths
if (['/api/auth/login'].includes(event.path)) {
return
}
// Get Token
const cookies = parseCookies(event)
const token = cookies.auth_token
if (token) {
const payload = verifyToken(token)
if (payload) {
// Verify user status
const user = await User.getSafeUserById(payload.userId)
if (user && user.status == 'active') {
event.context.user = user
return
}
}
}
// Authentication failed
if (event.path.startsWith('/api/admin/')) {
throw createError({
statusCode: 401,
message: 'No permission to access admin API'
})
}
})Data Model Development
typescript
// server/models/Article.ts
import { db } from 'server/database/config'
export const ArticleModel = {
// Find all articles
async findAll(limit: number = 20, offset: number = 0) {
const [rows] = await db.query(`
SELECT a.*, c.name as category_name
FROM articles a
LEFT JOIN categories c ON a.category_id = c.id
ORDER BY a.created_at DESC
LIMIT ? OFFSET ?
`, [limit, offset])
return rows
},
// Find article by ID
async findById(id: number) {
const [rows] = await db.query(`
SELECT a.*, c.name as category_name
FROM articles a
LEFT JOIN categories c ON a.category_id = c.id
WHERE a.id = ?
`, [id])
return rows[0]
},
// Create article
async create(data: any) {
const [result] = await db.query(`
INSERT INTO articles (title, content, summary, author_id, category_id)
VALUES (?, ?, ?, ?, ?)
`, [data.title, data.content, data.summary, data.author_id, data.category_id])
return result.insertId
},
// Update article
async update(id: number, data: any) {
const [result] = await db.query(`
UPDATE articles
SET title = ?, content = ?, summary = ?, category_id = ?, updated_at = NOW()
WHERE id = ?
`, [data.title, data.content, data.summary, data.category_id, id])
return result.affectedRows > 0
},
// Delete article
async delete(id: number) {
const [result] = await db.query('DELETE FROM articles WHERE id = ?', [id])
return result.affectedRows > 0
}
}Database Operations
Using MySQL Plugin
typescript
import { db } from 'server/database/config'
export default defineEventHandler(async (event) => {
// Query
const [users] = await db.query('SELECT * FROM users WHERE status = ?', ['active'])
// Insert
const [result] = await db.query(
'INSERT INTO users (username, email, password) VALUES (?, ?, ?)',
['test', 'test@example.com', 'hashed_password']
)
// Update
await db.query('UPDATE users SET nickname = ? WHERE id = ?', ['New Name', userId])
// Delete
await db.query('DELETE FROM users WHERE id = ?', [userId])
return { success: true }
})Using Transactions
typescript
import { db } from 'server/database/config'
export default defineEventHandler(async (event) => {
const connection = await db.getConnection()
try {
await connection.beginTransaction()
// Execute multiple operations
await connection.query('INSERT INTO articles (...) VALUES (...)', [...])
await connection.query('UPDATE categories SET count = count + 1 WHERE id = ?', [categoryId])
await connection.commit()
return { success: true }
} catch (error) {
await connection.rollback()
throw error
} finally {
connection.release()
}
})Code Standards
TypeScript Standards
typescript
// ✅ Good practices
interface User {
id: number
username: string
email: string
}
const getUser = async (id: number): Promise<User> => {
// ...
}
// ❌ Avoid
const getUser = async (id) => {
// Missing type annotations
}Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Component | PascalCase | UserProfile.vue |
| Utility function | camelCase | formatDate() |
| Constant | UPPER_SNAKE_CASE | API_BASE_URL |
| Interface/Type | PascalCase | UserProfile |
| Filename | kebab-case | user-profile.ts |
Comment Standards
typescript
/**
* User authentication service
* Provides user login, registration, Token validation and other functions
*/
export class AuthService {
/**
* User login
* @param username Username
* @param password Password
* @returns Login result
*/
static async login(username: string, password: string): Promise<LoginResult> {
// ...
}
}Debugging Techniques
Development Tools
bash
# Type checking
pnpm run type-check
# Code linting
pnpm run lint
# Auto fix
pnpm run lint:fixLog Debugging
typescript
export default defineEventHandler(async (event) => {
console.log('Request path:', event.path)
console.log('Query parameters:', getQuery(event))
console.log('Request body:', await readBody(event))
// ...
})Testing
TODO: Add testing framework and examples