Back to Blog

Building a Full-Stack FastAPI + Vue 3 Project Template

by Peter Szalontay, November 05, 2024

Building a Full-Stack FastAPI + Vue 3 Project Template

Automate Your Business with AI

Enterprise-grade AI agents customized for your needs

Discover Lazy AI for Business

Through my experience building several production applications with FastAPI and Vue, I've developed a template that combines the best of both worlds: FastAPI's powerful backend capabilities with Vue 3's reactive frontend features. This guide shares the approach that has successfully served applications handling thousands of daily users.

Personal Experience Note: When I first combined FastAPI and Vue, coordinating the development and build processes was challenging. After several production deployments, I've refined a setup that provides a smooth development experience while maintaining optimal production performance.

Project Structure

Here's the project structure I've found most effective for FastAPI + Vue applications:

fastapi-vue-project/
├── backend/
│   ├── app/
│   │   ├── __init__.py
│   │   ├── main.py
│   │   ├── core/
│   │   │   ├── config.py
│   │   │   └── security.py
│   │   ├── api/
│   │   │   ├── v1/
│   │   │   └── deps.py
│   │   ├── models/
│   │   ├── schemas/
│   │   └── services/
│   ├── requirements.txt
│   └── alembic/
├── frontend/
│   ├── src/
│   │   ├── assets/
│   │   ├── components/
│   │   ├── composables/
│   │   ├── stores/
│   │   ├── views/
│   │   ├── router/
│   │   ├── types/
│   │   ├── App.vue
│   │   └── main.ts
│   ├── public/
│   ├── index.html
│   ├── package.json
│   ├── tsconfig.json
│   └── vite.config.ts
├── docker/
│   ├── backend/
│   └── frontend/
├── nginx/
│   └── conf.d/
├── .env
├── docker-compose.yml
└── README.md

Backend Setup

First, let's set up the FastAPI backend with CORS support for Vue:

# backend/app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles

app = FastAPI(title="FastAPI + Vue App")

# Configure CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],  # Vue dev server
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# API routes
@app.get("/api/v1/health")
async def health_check():
    return {"status": "healthy"}

# Serve Vue app in production
app.mount("/", StaticFiles(directory="../frontend/dist", html=True), name="static")

Frontend Setup

Initialize a Vue 3 project with Vite and TypeScript:

# Create Vue project
npm create vite@latest frontend -- --template vue-ts

# Install essential dependencies
cd frontend
npm install vue-router@4 pinia @vueuse/core axios

Configure Vite for development and production:

// frontend/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true,
      },
    },
  },
  build: {
    outDir: 'dist',
    sourcemap: false,
  },
})

API Integration

Set up a type-safe API client:

// frontend/src/services/api.ts
import axios, { AxiosInstance, AxiosError } from 'axios'
import { useAuthStore } from '@/stores/auth'

export const createAPI = (): AxiosInstance => {
  const api = axios.create({
    baseURL: '/api/v1',
    headers: {
      'Content-Type': 'application/json',
    },
    withCredentials: true,
  })

  api.interceptors.request.use((config) => {
    const authStore = useAuthStore()
    if (authStore.token) {
      config.headers.Authorization = `Bearer ${authStore.token}`
    }
    return config
  })

  api.interceptors.response.use(
    (response) => response,
    async (error: AxiosError) => {
      if (error.response?.status === 401) {
        const authStore = useAuthStore()
        authStore.logout()
      }
      return Promise.reject(error)
    }
  )

  return api
}

State Management

Implement a Pinia store for authentication:

// frontend/src/stores/auth.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { User } from '@/types'
import { api } from '@/services/api'

export const useAuthStore = defineStore('auth', () => {
  const token = ref(localStorage.getItem('token'))
  const user = ref(null)

  async function login(credentials: { email: string; password: string }) {
    const response = await api.post('/auth/login', credentials)
    token.value = response.data.token
    user.value = response.data.user
    localStorage.setItem('token', token.value)
  }

  function logout() {
    token.value = null
    user.value = null
    localStorage.removeItem('token')
  }

  return { token, user, login, logout }
})

Vue Components Structure

Example of a well-structured Vue component:




Development Environment

Configure Docker Compose for development:

# docker-compose.yml
version: '3.8'

services:
  backend:
    build: 
      context: ./backend
      dockerfile: ../docker/backend/Dockerfile
    volumes:
      - ./backend:/app
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/dbname
    depends_on:
      - db

  frontend:
    build:
      context: ./frontend
      dockerfile: ../docker/frontend/Dockerfile
    volumes:
      - ./frontend:/app
      - /app/node_modules
    ports:
      - "5173:5173"
    environment:
      - VITE_API_URL=http://localhost:8000

  db:
    image: postgres:13
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=dbname
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Production Deployment

Configure Nginx as reverse proxy:

# nginx/conf.d/default.conf
server {
    listen 80;
    server_name localhost;

    location /api {
        proxy_pass http://backend:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }
}

Performance Optimization

Key optimizations for production:

// 1. Frontend optimization in vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'vue-vendor': ['vue', 'vue-router', 'pinia'],
          'utils': ['axios', '@vueuse/core'],
        }
      }
    }
  }
})

# 2. Backend caching
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend

@app.on_event("startup")
async def startup():
    redis = aioredis.from_url("redis://localhost")
    FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache:")

Conclusion

This template provides a solid foundation for building full-stack applications with FastAPI and Vue 3. The combination delivers excellent developer experience while maintaining high performance in production.

Remember: While this template provides a strong starting point, adapt it to your specific needs. Keep frontend and backend concerns separate, maintain type safety, and optimize for your deployment environment.

For more information, refer to the official documentation for FastAPI and Vue.js.

Automate Your Business with AI

Enterprise-grade AI agents customized for your needs

Discover Lazy AI for Business

Recent blog posts