Appearance
Frontend Project Structure
Overview
This section explains the frontend project structure using Vue.js with modern architecture patterns and best practices.
Technology Stack
Core Framework
- Vue.js 3 - Progressive JavaScript framework
- TypeScript - Type-safe JavaScript
- Vite - Fast build tool and dev server
State Management
- Pinia - Intuitive state management for Vue
- VueUse - Collection of Vue composition utilities
UI Framework
- Tailwind CSS - Utility-first CSS framework
- Headless UI - Unstyled, accessible UI components
Project Structure
Main Application Structure
frontend/
├── src/ # Main source code
│ ├── components/ # Reusable Vue components
│ │ ├── ui/ # Base UI components
│ │ │ ├── Button/
│ │ │ │ ├── Button.vue
│ │ │ │ ├── Button.test.ts
│ │ │ │ └── index.ts
│ │ │ ├── Input/
│ │ │ ├── Modal/
│ │ │ └── index.ts
│ │ ├── forms/ # Form components
│ │ ├── layout/ # Layout components
│ │ └── business/ # Business-specific components
│ ├── views/ # Page components
│ │ ├── Home/
│ │ │ ├── Home.vue
│ │ │ ├── Home.test.ts
│ │ │ └── index.ts
│ │ ├── Dashboard/
│ │ ├── Profile/
│ │ └── Auth/
│ ├── router/ # Vue Router configuration
│ │ ├── index.ts # Main router
│ │ ├── routes/ # Route definitions
│ │ │ ├── auth.ts
│ │ │ ├── dashboard.ts
│ │ │ └── index.ts
│ │ └── guards/ # Route guards
│ ├── stores/ # Pinia stores
│ │ ├── auth.ts # Authentication store
│ │ ├── user.ts # User store
│ │ ├── app.ts # App state store
│ │ └── index.ts
│ ├── services/ # API services
│ │ ├── api/ # API client
│ │ │ ├── client.ts # HTTP client setup
│ │ │ ├── auth.ts # Auth API
│ │ │ ├── users.ts # Users API
│ │ │ └── index.ts
│ │ ├── storage/ # Local storage utilities
│ │ └── validation/ # Form validation
│ ├── utils/ # Utility functions
│ │ ├── constants.ts # Application constants
│ │ ├── helpers.ts # Helper functions
│ │ ├── formatters.ts # Data formatters
│ │ └── validators.ts # Validation utilities
│ ├── types/ # TypeScript type definitions
│ │ ├── api.ts # API response types
│ │ ├── components.ts # Component prop types
│ │ └── global.ts # Global types
│ ├── composables/ # Vue composables
│ │ ├── useAuth.ts # Authentication composable
│ │ ├── useApi.ts # API composable
│ │ ├── useForm.ts # Form handling composable
│ │ └── useToast.ts # Toast notifications
│ ├── assets/ # Static assets
│ │ ├── images/ # Image files
│ │ ├── icons/ # Icon files
│ │ ├── styles/ # Global styles
│ │ │ ├── main.css # Main stylesheet
│ │ │ ├── variables.css # CSS variables
│ │ │ └── utilities.css # Utility classes
│ │ └── fonts/ # Font files
│ ├── plugins/ # Vue plugins
│ │ ├── i18n.ts # Internationalization
│ │ ├── toast.ts # Toast notifications
│ │ └── index.ts
│ ├── layouts/ # Layout components
│ │ ├── DefaultLayout.vue
│ │ ├── AuthLayout.vue
│ │ └── DashboardLayout.vue
│ ├── middleware/ # Route middleware
│ │ ├── auth.ts # Authentication middleware
│ │ ├── guest.ts # Guest-only middleware
│ │ └── index.ts
│ ├── config/ # Configuration files
│ │ ├── app.ts # App configuration
│ │ ├── api.ts # API configuration
│ │ └── constants.ts # App constants
│ └── main.ts # Application entry point
├── public/ # Public static files
│ ├── favicon.ico
│ ├── robots.txt
│ └── manifest.json
├── tests/ # Test files
│ ├── unit/ # Unit tests
│ ├── integration/ # Integration tests
│ ├── e2e/ # End-to-end tests
│ ├── fixtures/ # Test data
│ └── setup/ # Test setup files
├── docs/ # Component documentation
├── .env.example # Environment variables example
├── .env.local # Local environment variables
├── vite.config.ts # Vite configuration
├── tsconfig.json # TypeScript configuration
├── tailwind.config.js # Tailwind CSS configuration
├── package.json # Dependencies and scripts
└── README.md # Project documentationComponent Architecture
Component Structure
Each component should follow this structure:
ComponentName/
├── ComponentName.vue # Main component file
├── ComponentName.test.ts # Unit tests
├── ComponentName.stories.ts # Storybook stories (optional)
├── types.ts # Component-specific types
└── index.ts # Component exportComponent Naming Conventions
- PascalCase for component names:
UserProfile.vue - kebab-case for file names:
user-profile.vue - camelCase for props and events:
userName,onUserUpdate
Component Categories
1. UI Components (Base)
vue
<!-- src/components/ui/Button/Button.vue -->
<template>
<button
:class="buttonClasses"
:disabled="disabled"
@click="handleClick"
>
<slot />
</button>
</template>
<script setup lang="ts">
interface Props {
variant?: 'primary' | 'secondary' | 'danger'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md',
disabled: false
})
const emit = defineEmits<{
click: [event: MouseEvent]
}>()
const buttonClasses = computed(() => [
'btn',
`btn-${props.variant}`,
`btn-${props.size}`,
{ 'btn-disabled': props.disabled }
])
const handleClick = (event: MouseEvent) => {
if (!props.disabled) {
emit('click', event)
}
}
</script>2. Business Components
vue
<!-- src/components/business/UserCard/UserCard.vue -->
<template>
<div class="user-card">
<img :src="user.avatar" :alt="user.name" class="user-avatar" />
<div class="user-info">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
<Button @click="handleEdit">Edit</Button>
</div>
</template>
<script setup lang="ts">
import { Button } from '@/components/ui'
interface User {
id: string
name: string
email: string
avatar: string
}
interface Props {
user: User
}
defineProps<Props>()
const emit = defineEmits<{
edit: [user: User]
}>()
const handleEdit = () => {
emit('edit', props.user)
}
</script>State Management with Pinia
Store Structure
typescript
// src/stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { User } from '@/types'
export const useAuthStore = defineStore('auth', () => {
// State
const user = ref<User | null>(null)
const token = ref<string | null>(null)
const isLoading = ref(false)
// Getters
const isAuthenticated = computed(() => !!token.value)
const userRole = computed(() => user.value?.role || 'guest')
// Actions
const login = async (credentials: LoginCredentials) => {
isLoading.value = true
try {
const response = await authApi.login(credentials)
user.value = response.user
token.value = response.token
localStorage.setItem('token', response.token)
} catch (error) {
throw error
} finally {
isLoading.value = false
}
}
const logout = () => {
user.value = null
token.value = null
localStorage.removeItem('token')
}
return {
// State
user,
token,
isLoading,
// Getters
isAuthenticated,
userRole,
// Actions
login,
logout
}
})Routing Configuration
Router Setup
typescript
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import { authGuard, guestGuard } from '@/middleware'
import routes from './routes'
const router = createRouter({
history: createWebHistory(),
routes
})
// Global guards
router.beforeEach(authGuard)
router.beforeEach(guestGuard)
export default routerRoute Definitions
typescript
// src/router/routes/auth.ts
import { RouteRecordRaw } from 'vue-router'
export const authRoutes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/Auth/Login.vue'),
meta: {
layout: 'auth',
requiresGuest: true
}
},
{
path: '/register',
name: 'Register',
component: () => import('@/views/Auth/Register.vue'),
meta: {
layout: 'auth',
requiresGuest: true
}
}
]API Integration
API Client Setup
typescript
// src/services/api/client.ts
import axios from 'axios'
import { useAuthStore } from '@/stores/auth'
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// Request interceptor
apiClient.interceptors.request.use(
(config) => {
const authStore = useAuthStore()
if (authStore.token) {
config.headers.Authorization = `Bearer ${authStore.token}`
}
return config
},
(error) => Promise.reject(error)
)
// Response interceptor
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
const authStore = useAuthStore()
authStore.logout()
router.push('/login')
}
return Promise.reject(error)
}
)
export default apiClientService Layer
typescript
// src/services/api/users.ts
import apiClient from './client'
import type { User, CreateUserRequest, UpdateUserRequest } from '@/types'
export const usersApi = {
getAll: () => apiClient.get<User[]>('/users'),
getById: (id: string) => apiClient.get<User>(`/users/${id}`),
create: (user: CreateUserRequest) =>
apiClient.post<User>('/users', user),
update: (id: string, user: UpdateUserRequest) =>
apiClient.put<User>(`/users/${id}`, user),
delete: (id: string) => apiClient.delete(`/users/${id}`)
}Composables
Custom Composables
typescript
// src/composables/useApi.ts
import { ref } from 'vue'
export function useApi<T>(apiCall: () => Promise<T>) {
const data = ref<T | null>(null)
const error = ref<Error | null>(null)
const loading = ref(false)
const execute = async () => {
loading.value = true
error.value = null
try {
data.value = await apiCall()
} catch (err) {
error.value = err as Error
} finally {
loading.value = false
}
}
return {
data,
error,
loading,
execute
}
}Testing Structure
Test Organization
tests/
├── unit/ # Unit tests
│ ├── components/ # Component tests
│ ├── stores/ # Store tests
│ ├── utils/ # Utility tests
│ └── composables/ # Composable tests
├── integration/ # Integration tests
├── e2e/ # End-to-end tests
├── fixtures/ # Test data
└── setup/ # Test setupComponent Testing Example
typescript
// tests/unit/components/ui/Button.test.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Button from '@/components/ui/Button/Button.vue'
describe('Button', () => {
it('renders slot content', () => {
const wrapper = mount(Button, {
slots: {
default: 'Click me'
}
})
expect(wrapper.text()).toBe('Click me')
})
it('emits click event when clicked', async () => {
const wrapper = mount(Button)
await wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
it('applies correct classes based on props', () => {
const wrapper = mount(Button, {
props: {
variant: 'danger',
size: 'lg'
}
})
expect(wrapper.classes()).toContain('btn-danger')
expect(wrapper.classes()).toContain('btn-lg')
})
})Build Configuration
Vite Configuration
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true
}
}
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router', 'pinia'],
ui: ['@headlessui/vue', '@heroicons/vue']
}
}
}
}
})Best Practices
Code Organization
- Component Composition: Use composition API for better logic reuse
- Type Safety: Leverage TypeScript for better development experience
- Performance: Use lazy loading for routes and components
- Accessibility: Follow WCAG guidelines for inclusive design
- Responsive Design: Mobile-first approach with Tailwind CSS
Development Workflow
- Component-Driven Development: Build components in isolation
- Testing: Write tests for all components and business logic
- Code Quality: Use ESLint and Prettier for consistent code style
- Git Workflow: Follow conventional commits and feature branches
- Documentation: Document complex components and business logic