Guidance for building fullstack apps with Vite (React + TypeScript) frontend and FastAPI backend. Use when demos need a web UI beyond what Streamlit provides.
Limited to specific tools
Additional assets for this skill
This skill is limited to using the following tools:
Use Streamlit if:
Use Vite + FastAPI if:
| Layer | Technology |
|---|---|
| Frontend | Vite + React + TypeScript |
| Styling | Tailwind CSS + Shadcn UI |
| Backend | FastAPI (Python) |
| Package Manager | pnpm |
| The Glue | OpenAPI (FastAPI auto-generates it) |
mkdir my-app && cd my-app
# Frontend
pnpm create vite@latest frontend --template react-ts
cd frontend
pnpm install
# Add Tailwind v4 (uses Vite plugin, NOT tailwind.config.js)
pnpm add tailwindcss @tailwindcss/vite
# Replace src/index.css contents with: @import "tailwindcss";
# Initialize Shadcn (use explicit flags to avoid interactive prompts)
pnpm dlx shadcn@latest init -y --base-color neutral
cd ..
# Backend
mkdir backend
cd backend
uv init
uv add fastapi uvicorn pydantic
IMPORTANT: Tailwind v4 uses a Vite plugin. Do NOT create a tailwind.config.js file.
// frontend/vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import path from 'path'
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"], # Vite dev server
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Alternative: Vite Proxy (avoids CORS entirely)
Instead of CORS middleware, you can proxy API requests through Vite. Add to vite.config.ts:
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
Then use /api/analyze instead of http://localhost:8000/analyze in your frontend fetch calls.
The biggest risk: Frontend guessing what backend built.
The solution: Use FastAPI's auto-generated OpenAPI spec as the contract.
uv run uvicorn main:app --reloadhttp://localhost:8000/openapi.json// Define types matching your Pydantic models
interface AnalyzeRequest {
text: string;
}
interface AnalyzeResponse {
score: number;
explanation: string;
}
// Type-safe fetch
async function analyzeText(data: AnalyzeRequest): Promise<AnalyzeResponse> {
const res = await fetch('http://localhost:8000/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
return res.json();
}
Critical: Shadcn components must be installed before use.
# Correct - install first
pnpm dlx shadcn@latest add button
# Then import
import { Button } from "@/components/ui/button"
# WRONG - this package doesn't exist
import { Button } from "shadcn-ui"
pnpm dlx shadcn@latest add button card input form dialog
/components/ui/ - Shadcn base components (CLI puts them here)/components/features/ - Your composed components using Shadcn# Terminal 1: Backend
cd backend
uv run uvicorn main:app --reload --port 8000
# Terminal 2: Frontend
cd frontend
pnpm run dev
# Opens at http://localhost:5173
For simple demos (1-2 endpoints), the patterns above are sufficient.
For more complex apps:
| Need | Tool |
|---|---|
| Auto-generate TypeScript client | npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client |
| Caching, loading states, refetching | TanStack Query (React Query) |
These add complexity - only use if the demo genuinely needs them.