DeployStack Frontend Development
The DeployStack frontend is a modern web application built with Vue 3, TypeScript, and Vite, specifically designed for managing MCP (Model Context Protocol) server deployments. This guide covers everything you need to know to develop and contribute to the frontend.
Technology Stack
- Framework: Vue 3 with Composition API
- Language: TypeScript for type safety
- Build Tool: Vite for fast development and building
- Styling: TailwindCSS with shadcn-vue components
- Icons: Lucide Icons
- Internationalization: Vue I18n
- State Management: Pinia (when needed)
- Routing: Vue Router
Quick Start
Prerequisites
- Node.js 18 or higher
- npm 8 or higher
Development Setup
-
Navigate to frontend directory:
cd services/frontend -
Install dependencies:
npm install -
Start development server:
npm run dev -
Build for production:
npm run build
The development server will start at http://localhost:5173 with hot module replacement enabled.
Project Structure
services/frontend/
├── src/
│ ├── components/ # Reusable Vue components
│ ├── views/ # Page-level components
│ ├── router/ # Vue Router configuration
│ ├── stores/ # Pinia stores (state management)
│ ├── services/ # API services and utilities
│ ├── plugins/ # Frontend plugin system
│ ├── i18n/ # Internationalization files
│ ├── utils/ # Utility functions
│ ├── types/ # TypeScript type definitions
│ └── assets/ # Static assets
├── public/ # Public static files
├── dist/ # Built application (generated)
└── ...Development Guidelines
Code Style
- Use TypeScript for all new code
- Follow Vue 3 Composition API patterns
- Use
<script setup>syntax for components - Maintain consistent naming conventions
- Add proper TypeScript types for all functions and variables
Component Development
Vue Component Structure
Always prefer Vue Single File Components (SFC) with <script setup> and <template> sections over TypeScript files with render functions.
✅ Preferred Approach - Vue SFC:
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { Button } from '@/components/ui/button'
import { Settings } from 'lucide-vue-next'
// Props with TypeScript
interface Props {
title: string
count?: number
onAction?: (id: string) => void
}
const props = withDefaults(defineProps<Props>(), {
count: 0
})
// Composables
const { t } = useI18n()
// Reactive state
const isVisible = ref(true)
// Computed properties
const displayTitle = computed(() =>
`${props.title} (${props.count})`
)
// Methods
function toggleVisibility() {
isVisible.value = !isVisible.value
}
function handleAction(id: string) {
props.onAction?.(id)
}
</script>
<template>
<div v-if="isVisible" class="component-container">
<h2 class="text-xl font-semibold">{{ displayTitle }}</h2>
<Button
@click="toggleVisibility"
class="mt-2"
variant="outline"
>
<Settings class="h-4 w-4 mr-2" />
{{ t('common.toggle') }}
</Button>
</div>
</template>❌ Avoid - TypeScript files with render functions:
// Don't create files like this for UI components
import { h } from 'vue'
import type { ColumnDef } from '@tanstack/vue-table'
export function createColumns(): ColumnDef[] {
return [
{
id: 'actions',
cell: ({ row }) => {
return h('div', { class: 'flex justify-end' }, [
h(Button, {
onClick: () => handleAction(row.original.id)
}, () => 'Action')
])
}
}
]
}Why Vue SFC is Preferred
- Better Developer Experience: Clear separation of logic, template, and styles
- Improved Readability: Template syntax is more intuitive than render functions
- Better Tooling Support: Vue DevTools, syntax highlighting, and IntelliSense work better
- Easier Maintenance: Future developers can understand and modify components more easily
- Vue 3 Best Practices: Aligns with official Vue 3 recommendations
Table Components Example
When creating table components, prefer this structure:
<script setup lang="ts">
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Settings } from 'lucide-vue-next'
interface Props {
items: Array<{
id: string
name: string
status: string
}>
onManage: (id: string) => void
}
const props = defineProps<Props>()
</script>
<template>
<div class="rounded-md border">
<table class="w-full">
<thead>
<tr class="border-b">
<th class="h-12 px-4 text-left align-middle font-medium">Name</th>
<th class="h-12 px-4 text-left align-middle font-medium">Status</th>
<th class="h-12 px-4 text-right align-middle font-medium">Actions</th>
</tr>
</thead>
<tbody>
<tr
v-for="item in items"
:key="item.id"
class="border-b transition-colors hover:bg-muted/50"
>
<td class="p-4 align-middle">
<div class="font-medium">{{ item.name }}</div>
</td>
<td class="p-4 align-middle">
<Badge :variant="item.status === 'active' ? 'default' : 'secondary'">
{{ item.status }}
</Badge>
</td>
<td class="p-4 align-middle">
<div class="flex justify-end">
<Button
variant="outline"
size="sm"
@click="props.onManage(item.id)"
>
<Settings class="h-4 w-4 mr-1" />
Manage
</Button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>UI Components and Styling
The frontend uses TailwindCSS for styling with shadcn-vue component library for consistent UI elements. For comprehensive styling guidelines, component patterns, and design standards, see the UI Design System documentation.
Environment Configuration
The frontend uses a sophisticated environment variable system that works seamlessly across development and production environments. For complete details on configuring and using environment variables, see the dedicated Environment Variables Guide.
Quick Start
Create environment files in the services/frontend directory:
# .env (base configuration)
VITE_DEPLOYSTACK_BACKEND_URL=http://localhost:3000
VITE_APP_TITLE=DeployStack
# .env.local (local overrides, gitignored)
VITE_DEPLOYSTACK_BACKEND_URL=http://localhost:3000
VITE_APP_TITLE=DeployStack (Development)Accessing Variables in Code
import { getEnv, getAllEnv } from '@/utils/env'
// Get specific variables
const backendUrl = getEnv('VITE_DEPLOYSTACK_BACKEND_URL')
const appTitle = getEnv('VITE_APP_TITLE')
// Get all variables (useful for debugging)
const allEnvVars = getAllEnv()API Integration
Service Layer Pattern
IMPORTANT: The frontend uses a service layer pattern with direct fetch() calls for API communication. This is the established pattern and must be followed for consistency.
✅ Required Pattern - Direct Fetch Calls
All API services must use direct fetch() calls instead of API client libraries. This ensures consistency across the codebase and simplifies maintenance.
// services/mcpServerService.ts
export class McpServerService {
private static baseUrl = getEnv('VITE_API_URL')
static async getAllServers(): Promise<McpServer[]> {
const response = await fetch(`${this.baseUrl}/api/mcp-servers`)
if (!response.ok) {
throw new Error('Failed to fetch MCP servers')
}
return response.json()
}
static async deployServer(serverId: string, config: DeployConfig): Promise<Deployment> {
const response = await fetch(`${this.baseUrl}/api/mcp-servers/${serverId}/deploy`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(config),
})
if (!response.ok) {
throw new Error('Failed to deploy MCP server')
}
return response.json()
}
}❌ Avoid - API Client Libraries
Do not use API client libraries like Axios, or custom API client wrappers:
// DON'T DO THIS
import axios from 'axios'
import { apiClient } from '@/utils/apiClient'
// Avoid these patterns
const response = await axios.get('/api/servers')
const data = await apiClient.get('/api/servers')Service Layer Guidelines
- Use Static Classes: All service methods should be static
- Direct Fetch: Always use native
fetch()API - Error Handling: Throw meaningful errors for failed requests
- Type Safety: Define proper TypeScript interfaces for requests/responses
- Consistent Naming: Use descriptive method names (e.g.,
getAllServers,createCategory) - Base URL: Always use environment variables for API endpoints
Using Services in Components
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { McpServerService } from '@/services/mcpServerService'
import type { McpServer } from '@/types/mcp'
const servers = ref<McpServer[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
async function fetchServers() {
isLoading.value = true
error.value = null
try {
servers.value = await McpServerService.getAllServers()
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error'
console.error('Failed to fetch servers:', err)
} finally {
isLoading.value = false
}
}
onMounted(() => {
fetchServers()
})
</script>Next Steps
Continue reading the detailed guides:
- UI Design System - Component patterns, styling guidelines, and design standards
- Environment Variables - Complete environment configuration guide
- Global Event Bus - Cross-component communication system
- Internationalization (i18n) - Multi-language support
- Plugin System - Extending functionality
Docker Development
Building the Frontend
# Build the Docker image
docker build -t deploystack/frontend:dev .
# Run with development configuration
docker run -it -p 8080:80 \
-e VITE_API_URL="http://localhost:3000" \
-e VITE_APP_TITLE="DeployStack (Docker Dev)" \
deploystack/frontend:devProduction Deployment
The frontend is designed to work seamlessly with the backend service:
# Production deployment
docker run -d -p 8080:80 \
-e VITE_API_URL="https://api.your-domain.com" \
-e VITE_APP_TITLE="DeployStack" \
deploystack/frontend:latest