Full-Stack FastAPI + React Tutorial 2025 - Build Modern Web Apps
Learn to build a complete full-stack web application using FastAPI (Python) and React (TypeScript) with modern tooling. This tutorial covers everything from project setup to API integration, using industry-standard tools and best practices.
What You'll Learn
By completing this tutorial, you'll master:
- FastAPI backend setup with Poetry dependency management
- React frontend development with TypeScript and Vite
- API integration between backend and frontend
- Modern UI development with Material-UI components
- Development workflow with hot reloading and proxy configuration
- Project structure following industry best practices
Prerequisites
What you need before starting:
- Python 3.8+ and Node.js 18+ installed
- Basic knowledge of Python and JavaScript/TypeScript
- Command line familiarity (terminal/command prompt)
- Code editor (VS Code recommended)
Time to complete: 45 minutes
What We're Building
You'll create a Task Management Dashboard with:
- FastAPI backend - REST API with task CRUD operations
- React frontend - Modern UI with Material-UI components
- Real-time updates - Live data synchronization
- TypeScript - Full type safety across the stack
- Vite - Lightning-fast development experience
Final result preview: A professional dashboard where users can create, view, and manage tasks with a clean Material-UI interface connected to a FastAPI backend.
Step 1: Project Setup and Structure
Create Project Directory
Recommended Project Structure
fullstack-task-app/
├── backend/ # FastAPI application
│ ├── app/
│ │ ├── main.py # FastAPI app entry point
│ │ ├── models.py # Data models
│ │ └── routers/ # API route handlers
│ ├── pyproject.toml # Python dependencies
│ └── poetry.lock
├── frontend/ # React application
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── services/ # API services
│ │ ├── types/ # TypeScript types
│ │ └── App.tsx # Main app component
│ ├── package.json
│ └── vite.config.ts
└── README.md
This structure separates concerns clearly and follows modern development practices.
Step 2: FastAPI Backend Setup
Create Backend Directory
Initialize Poetry Project
# Install Poetry if not already installed
curl -sSL https://install.python-poetry.org | python3 -
# Initialize new Poetry project
poetry init --no-interaction
Install Dependencies
# Core dependencies
poetry add fastapi uvicorn[standard] pydantic
# Development dependencies
poetry add --group dev pytest httpx
Create FastAPI Application
Create backend/app/main.py
:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Optional
import uuid
from datetime import datetime
app = FastAPI(
title="Task Management API",
description="A modern task management API built with FastAPI",
version="1.0.0"
)
# Configure CORS for React frontend
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"], # Vite default port
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Data models
class TaskCreate(BaseModel):
title: str
description: Optional[str] = None
class Task(BaseModel):
id: str
title: str
description: Optional[str] = None
completed: bool = False
created_at: datetime
# In-memory storage (use database in production)
tasks_db: List[Task] = []
@app.get("/")
async def root():
return {"message": "Task Management API", "version": "1.0.0"}
@app.get("/tasks", response_model=List[Task])
async def get_tasks():
return tasks_db
@app.post("/tasks", response_model=Task)
async def create_task(task: TaskCreate):
new_task = Task(
id=str(uuid.uuid4()),
title=task.title,
description=task.description,
created_at=datetime.now()
)
tasks_db.append(new_task)
return new_task
@app.put("/tasks/{task_id}", response_model=Task)
async def update_task(task_id: str, task_update: TaskCreate):
for task in tasks_db:
if task.id == task_id:
task.title = task_update.title
task.description = task_update.description
return task
return {"error": "Task not found"}
@app.delete("/tasks/{task_id}")
async def delete_task(task_id: str):
global tasks_db
tasks_db = [task for task in tasks_db if task.id != task_id]
return {"message": "Task deleted"}
Test Backend Setup
Visit http://localhost:8000/docs
to see the interactive API documentation.
Step 3: React Frontend Setup
Create React App with Vite
# Navigate back to project root
cd ..
# Create React app with TypeScript
npm create vite@latest frontend -- --template react-ts
cd frontend
Install Dependencies
# Install Material-UI and dependencies
npm install @mui/material @emotion/react @emotion/styled @mui/icons-material
# Install additional utilities
npm install axios @types/axios
# Install development dependencies
npm install --save-dev @types/node
Configure Vite for API Proxy
Update frontend/vite.config.ts
:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
})
Create TypeScript Types
Create frontend/src/types/index.ts
:
export interface Task {
id: string;
title: string;
description?: string;
completed: boolean;
created_at: string;
}
export interface TaskCreate {
title: string;
description?: string;
}
Create API Service
Create frontend/src/services/api.ts
:
import axios from 'axios';
import { Task, TaskCreate } from '../types';
const API_BASE_URL = '/api';
const api = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
export const taskAPI = {
// Get all tasks
getTasks: async (): Promise<Task[]> => {
const response = await api.get<Task[]>('/tasks');
return response.data;
},
// Create new task
createTask: async (task: TaskCreate): Promise<Task> => {
const response = await api.post<Task>('/tasks', task);
return response.data;
},
// Update task
updateTask: async (id: string, task: TaskCreate): Promise<Task> => {
const response = await api.put<Task>(`/tasks/${id}`, task);
return response.data;
},
// Delete task
deleteTask: async (id: string): Promise<void> => {
await api.delete(`/tasks/${id}`);
},
};
Step 4: Build the React Interface
Create Task Components
Create frontend/src/components/TaskList.tsx
:
import React from 'react';
import {
List,
ListItem,
ListItemText,
ListItemSecondaryAction,
IconButton,
Paper,
Typography,
Box,
} from '@mui/material';
import { Delete as DeleteIcon } from '@mui/icons-material';
import { Task } from '../types';
interface TaskListProps {
tasks: Task[];
onDeleteTask: (id: string) => void;
}
const TaskList: React.FC<TaskListProps> = ({ tasks, onDeleteTask }) => {
if (tasks.length === 0) {
return (
<Paper sx={{ p: 3, textAlign: 'center' }}>
<Typography variant="h6" color="text.secondary">
No tasks yet. Create your first task above!
</Typography>
</Paper>
);
}
return (
<Paper sx={{ mt: 2 }}>
<List>
{tasks.map((task) => (
<ListItem key={task.id} divider>
<ListItemText
primary={task.title}
secondary={
<Box>
{task.description && (
<Typography variant="body2" color="text.secondary">
{task.description}
</Typography>
)}
<Typography variant="caption" color="text.secondary">
Created: {new Date(task.created_at).toLocaleDateString()}
</Typography>
</Box>
}
/>
<ListItemSecondaryAction>
<IconButton
edge="end"
aria-label="delete"
onClick={() => onDeleteTask(task.id)}
color="error"
>
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
</Paper>
);
};
export default TaskList;
Create frontend/src/components/TaskForm.tsx
:
import React, { useState } from 'react';
import {
Paper,
TextField,
Button,
Box,
Typography,
} from '@mui/material';
import { TaskCreate } from '../types';
interface TaskFormProps {
onCreateTask: (task: TaskCreate) => void;
loading?: boolean;
}
const TaskForm: React.FC<TaskFormProps> = ({ onCreateTask, loading = false }) => {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (title.trim()) {
onCreateTask({
title: title.trim(),
description: description.trim() || undefined,
});
setTitle('');
setDescription('');
}
};
return (
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
Create New Task
</Typography>
<Box component="form" onSubmit={handleSubmit}>
<TextField
fullWidth
label="Task Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
margin="normal"
required
disabled={loading}
/>
<TextField
fullWidth
label="Description (optional)"
value={description}
onChange={(e) => setDescription(e.target.value)}
margin="normal"
multiline
rows={3}
disabled={loading}
/>
<Button
type="submit"
variant="contained"
fullWidth
sx={{ mt: 2 }}
disabled={!title.trim() || loading}
>
{loading ? 'Creating...' : 'Create Task'}
</Button>
</Box>
</Paper>
);
};
export default TaskForm;
Update Main App Component
Update frontend/src/App.tsx
:
import React, { useState, useEffect } from 'react';
import {
Container,
Typography,
Box,
Alert,
CircularProgress,
} from '@mui/material';
import { Task, TaskCreate } from './types';
import { taskAPI } from './services/api';
import TaskForm from './components/TaskForm';
import TaskList from './components/TaskList';
const App: React.FC = () => {
const [tasks, setTasks] = useState<Task[]>([]);
const [loading, setLoading] = useState(true);
const [creating, setCreating] = useState(false);
const [error, setError] = useState<string | null>(null);
// Load tasks on component mount
useEffect(() => {
loadTasks();
}, []);
const loadTasks = async () => {
try {
setLoading(true);
const fetchedTasks = await taskAPI.getTasks();
setTasks(fetchedTasks);
setError(null);
} catch (err) {
setError('Failed to load tasks. Make sure the backend is running.');
console.error('Error loading tasks:', err);
} finally {
setLoading(false);
}
};
const handleCreateTask = async (taskData: TaskCreate) => {
try {
setCreating(true);
const newTask = await taskAPI.createTask(taskData);
setTasks([...tasks, newTask]);
setError(null);
} catch (err) {
setError('Failed to create task. Please try again.');
console.error('Error creating task:', err);
} finally {
setCreating(false);
}
};
const handleDeleteTask = async (taskId: string) => {
try {
await taskAPI.deleteTask(taskId);
setTasks(tasks.filter(task => task.id !== taskId));
setError(null);
} catch (err) {
setError('Failed to delete task. Please try again.');
console.error('Error deleting task:', err);
}
};
if (loading) {
return (
<Container maxWidth="md" sx={{ mt: 4, textAlign: 'center' }}>
<CircularProgress />
<Typography variant="h6" sx={{ mt: 2 }}>
Loading tasks...
</Typography>
</Container>
);
}
return (
<Container maxWidth="md" sx={{ mt: 4, mb: 4 }}>
<Box sx={{ mb: 4 }}>
<Typography variant="h3" component="h1" gutterBottom>
Task Management Dashboard
</Typography>
<Typography variant="subtitle1" color="text.secondary">
Full-stack application built with FastAPI and React
</Typography>
</Box>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<TaskForm onCreateTask={handleCreateTask} loading={creating} />
<TaskList tasks={tasks} onDeleteTask={handleDeleteTask} />
</Container>
);
};
export default App;
Step 5: Run the Full-Stack Application
Start Both Servers
Terminal 1 - Backend:
Terminal 2 - Frontend:
Test the Application
- Open your browser to
http://localhost:5173
- Create tasks using the form
- View tasks in the list below
- Delete tasks using the delete button
- Check API docs at
http://localhost:8000/docs
Step 6: Production Considerations
Environment Variables
Create frontend/.env
:
Update the API service to use environment variables:
Build for Production
# Build frontend
cd frontend
npm run build
# Backend is production-ready with uvicorn
cd ../backend
poetry run uvicorn app.main:app --host 0.0.0.0 --port 8000
Troubleshooting
Common Issues & Solutions
CORS Errors: - Ensure CORS middleware is configured in FastAPI - Check that frontend URL matches the allowed origins
API Connection Failed: - Verify both servers are running - Check that ports 8000 (backend) and 5173 (frontend) are available - Confirm Vite proxy configuration is correct
Type Errors: - Ensure TypeScript types match your API responses - Check that all imports are correct
Dependencies Issues:
# Clear node modules and reinstall
rm -rf node_modules package-lock.json
npm install
# Update Poetry dependencies
poetry update
What You've Accomplished
Congratulations! You've built a complete full-stack application featuring:
- Modern FastAPI backend with automatic API documentation
- React frontend with TypeScript and Material-UI
- Real-time data synchronization between frontend and backend
- Professional project structure following industry standards
- Development workflow with hot reloading and proxy configuration
- Production-ready setup with environment configuration
Next Steps
Immediate Enhancements:
- Add database integration - Replace in-memory storage with PostgreSQL
- Implement authentication - Add user registration and login
- Add task editing - Allow users to modify existing tasks
- Include task status - Add completed/pending states
- Add search and filtering - Enhance task management capabilities
Advanced Features:
- Real-time updates with WebSockets
- File upload functionality for task attachments
- Email notifications for task reminders
- API rate limiting and security enhancements
- Automated testing for both frontend and backend
Deployment Options:
- Frontend: Vercel, Netlify, or AWS S3
- Backend: Railway, Heroku, or AWS EC2
- Database: PostgreSQL on Railway or AWS RDS
Pro Tips for Full-Stack Development
Development Best Practices:
- Use TypeScript everywhere - Catch errors early and improve code quality
- Implement proper error handling - Graceful failures improve user experience
- Add loading states - Keep users informed during async operations
- Follow REST conventions - Consistent API design improves maintainability
- Use environment variables - Keep configuration separate from code
Performance Optimization:
- Implement caching - Reduce API calls with smart caching strategies
- Add request debouncing - Prevent excessive API calls from user input
- Optimize bundle size - Use dynamic imports and code splitting
- Database indexing - Improve query performance with proper indexes
Need a Production-Ready Foundation?
While this tutorial provides a solid foundation, building a production-ready application requires additional features like authentication, database integration, testing, and deployment automation.
Our AI Velocity boilerplate includes everything from this tutorial plus:
- Advanced authentication system with JWT and OAuth
- PostgreSQL database integration with migrations
- Comprehensive testing suite
- CI/CD pipeline setup
- Production deployment scripts
- AI-powered development tools
Ready to build amazing full-stack applications? You now have the foundation to create modern, scalable web applications with FastAPI and React. Start building your next project with confidence!