A full-stack application with React/TypeScript frontend and Python/FastAPI backend.
- Node.js (v16 or higher)
- Python 3.8 or higher
- uv (Python package manager)
curl -LsSf https://astral.sh/uv/install.sh | shAfter installation, restart your terminal or run:
source ~/.zshrcmock-chatbot/
├── frontend/ # React/TypeScript frontend
├── backend/ # Python/FastAPI backend
├── package.json # Root package.json for managing both apps
└── README.md # This file
-
Install dependencies:
npm run install:all
-
Start development servers:
npm run dev
This will start both:
- Frontend: http://localhost:3000
- Backend: http://localhost:8000
-
Access the application:
- Frontend: http://localhost:3000
- Backend API: http://localhost:8000
- API Documentation: http://localhost:8000/docs
npm run dev- Start both frontend and backend in development modenpm run dev:frontend- Start only the frontendnpm run dev:backend- Start only the backendnpm run install:all- Install all dependenciesnpm run build- Build the frontend for productionnpm run start- Start the frontend in production modenpm run backend:install- Install backend dependencies with uvnpm run backend:add- Add a new Python package to backendnpm run backend:run- Run a Python script in the backend environment
- Located in
frontend/ - Uses Vite for fast development
- TypeScript for type safety
- Tailwind CSS for styling
- Located in
backend/ - FastAPI for the API framework
- uv for dependency management
- Automatic API documentation at
/docs - CORS configured for frontend communication
# Add a production dependency
npm run backend:add fastapi
# Add a development dependency
cd backend && uv add --dev pytest# Run a Python script
npm run backend:run python script.py
# Run with specific Python version
cd backend && uv run --python 3.11 python script.py# Sync dependencies (install/update)
cd backend && uv sync
# Show dependency tree
cd backend && uv tree
# Update dependencies
cd backend && uv lock --upgrade
```toml:backend/pyproject.toml
[project]
name = "mock-chatbot-backend"
version = "0.1.0"
description = "FastAPI backend for mock chatbot"
requires-python = ">=3.8"
dependencies = [
"fastapi>=0.104.0",
"uvicorn[standard]>=0.24.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.uv]
dev-dependencies = [
"pytest>=7.0.0",
"black>=23.0.0",
"isort>=5.0.0",
]
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
app = FastAPI()
# Allow CORS for local frontend
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class ChatRequest(BaseModel):
message: str
class ChatResponse(BaseModel):
response: str
@app.post("/chat", response_model=ChatResponse)
async def chat_endpoint(request: ChatRequest):
# Simple echo bot logic
return ChatResponse(response=f"You said: {request.message}")Now let me create all the frontend files as well:
{
"name": "mock-chatbot-frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"axios": "^1.6.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.16",
"eslint": "^8.55.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"postcss": "^8.4.32",
"tailwindcss": "^3.3.6",
"typescript": "^5.2.2",
"vite": "^5.0.8"
}
}import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}){
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mock Chatbot</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}import { useState, useEffect } from 'react'
import axios from 'axios'
interface Message {
id: string
text: string
sender: 'user' | 'bot'
timestamp: Date
}
function App() {
const [messages, setMessages] = useState<Message[]>([])
const [inputText, setInputText] = useState('')
const [isLoading, setIsLoading] = useState(false)
const sendMessage = async () => {
if (!inputText.trim()) return
const userMessage: Message = {
id: Date.now().toString(),
text: inputText,
sender: 'user',
timestamp: new Date()
}
setMessages(prev => [...prev, userMessage])
setInputText('')
setIsLoading(true)
try {
const response = await axios.post('/api/chat', {
message: inputText
})
const botMessage: Message = {
id: (Date.now() + 1).toString(),
text: response.data.response,
sender: 'bot',
timestamp: new Date()
}
setMessages(prev => [...prev, botMessage])
} catch (error) {
console.error('Error sending message:', error)
const errorMessage: Message = {
id: (Date.now() + 1).toString(),
text: 'Sorry, I encountered an error. Please try again.',
sender: 'bot',
timestamp: new Date()
}
setMessages(prev => [...prev, errorMessage])
} finally {
setIsLoading(false)
}
}
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
sendMessage()
}
}
return (
<div className="min-h-screen bg-gray-100 flex flex-col">
<div className="bg-white shadow-sm border-b">
<div className="max-w-4xl mx-auto px-4 py-6">
<h1 className="text-3xl font-bold text-gray-900">Mock Chatbot</h1>
<p className="text-gray-600 mt-2">A simple chatbot with React and FastAPI</p>
</div>
</div>
<div className="flex-1 max-w-4xl mx-auto w-full px-4 py-6">
<div className="bg-white rounded-lg shadow-sm border h-96 flex flex-col">
{/* Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.length === 0 && (
<div className="text-center text-gray-500 mt-8">
Start a conversation by typing a message below!
</div>
)}
{messages.map((message) => (
<div
key={message.id}
className={`flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-xs lg:max-w-md px-4 py-2 rounded-lg ${
message.sender === 'user'
? 'bg-blue-500 text-white'
: 'bg-gray-200 text-gray-800'
}`}
>
{message.text}
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-gray-200 text-gray-800 px-4 py-2 rounded-lg">
<div className="flex space-x-1">
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
</div>
</div>
</div>
)}
</div>
{/* Input */}
<div className="border-t p-4">
<div className="flex space-x-2">
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="Type your message..."
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
disabled={isLoading}
/>
<button
onClick={sendMessage}
disabled={isLoading || !inputText.trim()}
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
Send
</button>
</div>
</div>
</div>
</div>
</div>
)
}
export default AppYou now have a full-stack boilerplate using React/TypeScript (frontend) and FastAPI (backend) with uv for Python dependency management.
Next steps:
-
Install uv (if you haven't already):
curl -LsSf https://astral.sh/uv/install.sh | sh source ~/.zshrc
-
Install all dependencies:
npm run install:all
-
Start both servers:
npm run dev
- Frontend: http://localhost:3000
- Backend: http://localhost:8000
Let me know if you want to proceed with file creation, or if you need any further customization!