React + Flask Full-Stack Application
This comprehensive example demonstrates building a modern full-stack web application using PPM to manage both React (JavaScript) and Flask (Python) dependencies in a unified workflow.What We’re Building
A Task Management Application with:- React frontend with TypeScript and modern UI
- Flask backend with REST API and database
- Real-time features with WebSocket support
- Data processing with Pandas for analytics
- Unified development with PPM
Project Setup
1. Initialize the Project
Copy
ppm init task-manager --template web
cd task-manager
2. Project Structure
Copy
task-manager/
├── project.toml # PPM configuration
├── frontend/ # React application
│ ├── src/
│ │ ├── components/
│ │ ├── pages/
│ │ ├── hooks/
│ │ ├── services/
│ │ └── App.tsx
│ ├── package.json
│ └── vite.config.ts
├── backend/ # Flask application
│ ├── app/
│ │ ├── models/
│ │ ├── routes/
│ │ ├── services/
│ │ └── __init__.py
│ ├── app.py
│ ├── requirements.txt
│ └── config.py
└── README.md
Configuration
project.toml
Copy
[project]
name = "task-manager"
version = "1.0.0"
description = "Full-stack task management application"
author = "Your Name"
license = "MIT"
[dependencies.js]
# React and core dependencies
react = "^18.2.0"
react-dom = "^18.2.0"
react-router-dom = "^6.15.0"
# TypeScript
typescript = "^5.0.0"
"@types/react" = "^18.2.0"
"@types/react-dom" = "^18.2.0"
# UI and styling
"@mui/material" = "^5.14.0"
"@mui/icons-material" = "^5.14.0"
"@emotion/react" = "^11.11.0"
"@emotion/styled" = "^11.11.0"
# HTTP client and utilities
axios = "^1.6.0"
"@tanstack/react-query" = "^4.35.0"
date-fns = "^2.30.0"
# Development tools
vite = "^5.0.0"
"@vitejs/plugin-react" = "^4.0.0"
[dependencies.python]
# Flask and web dependencies
flask = "^3.0.0"
flask-cors = "^4.0.0"
flask-sqlalchemy = "^3.0.0"
flask-migrate = "^4.0.0"
flask-jwt-extended = "^4.5.0"
flask-socketio = "^5.3.0"
# Database and ORM
sqlalchemy = "^2.0.0"
psycopg2-binary = "^2.9.0" # PostgreSQL adapter
# Data processing and utilities
pandas = "^2.0.0"
numpy = "^1.24.0"
python-dotenv = "^1.0.0"
bcrypt = "^4.0.0"
# Development and testing
pytest = "^7.4.0"
pytest-flask = "^1.2.0"
black = "^23.0.0"
flake8 = "^6.0.0"
[scripts]
# Development
dev = "ppm run dev:parallel"
"dev:frontend" = "cd frontend && npm run dev"
"dev:backend" = "cd backend && python app.py"
"dev:parallel" = "ppm run dev:backend & ppm run dev:frontend"
# Building
build = "ppm run build:frontend"
"build:frontend" = "cd frontend && npm run build"
# Testing
test = "ppm run test:frontend && ppm run test:backend"
"test:frontend" = "cd frontend && npm test"
"test:backend" = "cd backend && pytest"
# Database
"db:init" = "cd backend && flask db init"
"db:migrate" = "cd backend && flask db migrate -m 'Auto migration'"
"db:upgrade" = "cd backend && flask db upgrade"
"db:seed" = "cd backend && python seed.py"
# Code quality
lint = "ppm run lint:frontend && ppm run lint:backend"
"lint:frontend" = "cd frontend && npx eslint src"
"lint:backend" = "cd backend && flake8 ."
format = "ppm run format:frontend && ppm run format:backend"
"format:frontend" = "cd frontend && npx prettier --write src"
"format:backend" = "cd backend && black ."
Frontend Implementation
React App Structure
frontend/src/App.tsx:Copy
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { Container } from '@mui/material';
import Header from './components/Header';
import TaskList from './pages/TaskList';
import Dashboard from './pages/Dashboard';
import Analytics from './pages/Analytics';
const queryClient = new QueryClient();
const theme = createTheme({
palette: {
mode: 'light',
primary: {
main: '#1976d2',
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Router>
<Header />
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/tasks" element={<TaskList />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Container>
</Router>
</ThemeProvider>
</QueryClientProvider>
);
}
export default App;
Task Management Components
frontend/src/components/TaskCard.tsx:Copy
import React from 'react';
import {
Card,
CardContent,
CardActions,
Typography,
Button,
Chip,
Box,
} from '@mui/material';
import { format } from 'date-fns';
interface Task {
id: number;
title: string;
description: string;
status: 'todo' | 'in_progress' | 'done';
priority: 'low' | 'medium' | 'high';
due_date: string;
created_at: string;
}
interface TaskCardProps {
task: Task;
onStatusChange: (taskId: number, status: string) => void;
onDelete: (taskId: number) => void;
}
const TaskCard: React.FC<TaskCardProps> = ({ task, onStatusChange, onDelete }) => {
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'high': return 'error';
case 'medium': return 'warning';
case 'low': return 'success';
default: return 'default';
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'done': return 'success';
case 'in_progress': return 'info';
case 'todo': return 'default';
default: return 'default';
}
};
return (
<Card sx={{ mb: 2 }}>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="flex-start" mb={1}>
<Typography variant="h6" component="h2">
{task.title}
</Typography>
<Box>
<Chip
label={task.priority}
color={getPriorityColor(task.priority)}
size="small"
sx={{ mr: 1 }}
/>
<Chip
label={task.status}
color={getStatusColor(task.status)}
size="small"
/>
</Box>
</Box>
<Typography variant="body2" color="text.secondary" paragraph>
{task.description}
</Typography>
<Typography variant="caption" display="block">
Due: {format(new Date(task.due_date), 'MMM dd, yyyy')}
</Typography>
</CardContent>
<CardActions>
<Button
size="small"
onClick={() => onStatusChange(task.id, 'in_progress')}
disabled={task.status === 'in_progress'}
>
Start
</Button>
<Button
size="small"
onClick={() => onStatusChange(task.id, 'done')}
disabled={task.status === 'done'}
>
Complete
</Button>
<Button
size="small"
color="error"
onClick={() => onDelete(task.id)}
>
Delete
</Button>
</CardActions>
</Card>
);
};
export default TaskCard;
API Service
frontend/src/services/api.ts:Copy
import axios from 'axios';
const API_BASE_URL = 'http://localhost:5000/api';
const api = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
export interface Task {
id?: number;
title: string;
description: string;
status: 'todo' | 'in_progress' | 'done';
priority: 'low' | 'medium' | 'high';
due_date: string;
}
export interface TaskStats {
total: number;
completed: number;
in_progress: number;
overdue: number;
completion_rate: number;
}
export const taskApi = {
// Get all tasks
getTasks: async (): Promise<Task[]> => {
const response = await api.get('/tasks');
return response.data;
},
// Create new task
createTask: async (task: Omit<Task, 'id'>): Promise<Task> => {
const response = await api.post('/tasks', task);
return response.data;
},
// Update task
updateTask: async (id: number, updates: Partial<Task>): Promise<Task> => {
const response = await api.put(`/tasks/${id}`, updates);
return response.data;
},
// Delete task
deleteTask: async (id: number): Promise<void> => {
await api.delete(`/tasks/${id}`);
},
// Get task statistics
getStats: async (): Promise<TaskStats> => {
const response = await api.get('/tasks/stats');
return response.data;
},
// Get analytics data
getAnalytics: async () => {
const response = await api.get('/analytics');
return response.data;
},
};
Backend Implementation
Flask Application Structure
backend/app.py:Copy
from flask import Flask, request, jsonify
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from datetime import datetime, timedelta
import pandas as pd
import os
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev-secret-key')
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv(
'DATABASE_URL',
'sqlite:///tasks.db'
)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)
CORS(app)
# Models
class Task(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text)
status = db.Column(db.String(20), default='todo')
priority = db.Column(db.String(10), default='medium')
due_date = db.Column(db.DateTime, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'description': self.description,
'status': self.status,
'priority': self.priority,
'due_date': self.due_date.isoformat(),
'created_at': self.created_at.isoformat(),
'updated_at': self.updated_at.isoformat()
}
# Routes
@app.route('/api/tasks', methods=['GET'])
def get_tasks():
"""Get all tasks with optional filtering"""
status = request.args.get('status')
priority = request.args.get('priority')
query = Task.query
if status:
query = query.filter_by(status=status)
if priority:
query = query.filter_by(priority=priority)
tasks = query.order_by(Task.due_date.asc()).all()
return jsonify([task.to_dict() for task in tasks])
@app.route('/api/tasks', methods=['POST'])
def create_task():
"""Create a new task"""
data = request.get_json()
try:
task = Task(
title=data['title'],
description=data.get('description', ''),
status=data.get('status', 'todo'),
priority=data.get('priority', 'medium'),
due_date=datetime.fromisoformat(data['due_date'].replace('Z', '+00:00'))
)
db.session.add(task)
db.session.commit()
return jsonify(task.to_dict()), 201
except Exception as e:
return jsonify({'error': str(e)}), 400
@app.route('/api/tasks/<int:task_id>', methods=['PUT'])
def update_task(task_id):
"""Update an existing task"""
task = Task.query.get_or_404(task_id)
data = request.get_json()
try:
if 'title' in data:
task.title = data['title']
if 'description' in data:
task.description = data['description']
if 'status' in data:
task.status = data['status']
if 'priority' in data:
task.priority = data['priority']
if 'due_date' in data:
task.due_date = datetime.fromisoformat(data['due_date'].replace('Z', '+00:00'))
task.updated_at = datetime.utcnow()
db.session.commit()
return jsonify(task.to_dict())
except Exception as e:
return jsonify({'error': str(e)}), 400
@app.route('/api/tasks/<int:task_id>', methods=['DELETE'])
def delete_task(task_id):
"""Delete a task"""
task = Task.query.get_or_404(task_id)
db.session.delete(task)
db.session.commit()
return '', 204
@app.route('/api/tasks/stats', methods=['GET'])
def get_task_stats():
"""Get task statistics using Pandas for analysis"""
tasks = Task.query.all()
if not tasks:
return jsonify({
'total': 0,
'completed': 0,
'in_progress': 0,
'overdue': 0,
'completion_rate': 0
})
# Convert to DataFrame for analysis
df = pd.DataFrame([task.to_dict() for task in tasks])
df['due_date'] = pd.to_datetime(df['due_date'])
# Calculate statistics
total = len(df)
completed = len(df[df['status'] == 'done'])
in_progress = len(df[df['status'] == 'in_progress'])
overdue = len(df[(df['status'] != 'done') & (df['due_date'] < datetime.now())])
completion_rate = (completed / total * 100) if total > 0 else 0
return jsonify({
'total': total,
'completed': completed,
'in_progress': in_progress,
'overdue': overdue,
'completion_rate': round(completion_rate, 2)
})
@app.route('/api/analytics', methods=['GET'])
def get_analytics():
"""Get detailed analytics using Pandas"""
tasks = Task.query.all()
if not tasks:
return jsonify({
'productivity_by_day': [],
'priority_distribution': {},
'completion_trends': []
})
df = pd.DataFrame([task.to_dict() for task in tasks])
df['created_at'] = pd.to_datetime(df['created_at'])
df['due_date'] = pd.to_datetime(df['due_date'])
# Productivity by day of week
df['day_of_week'] = df['created_at'].dt.day_name()
productivity_by_day = df.groupby('day_of_week').size().to_dict()
# Priority distribution
priority_distribution = df['priority'].value_counts().to_dict()
# Completion trends (last 30 days)
thirty_days_ago = datetime.now() - timedelta(days=30)
recent_df = df[df['created_at'] >= thirty_days_ago]
completion_trends = recent_df.groupby(recent_df['created_at'].dt.date)['status'].apply(
lambda x: (x == 'done').sum()
).to_dict()
# Convert date keys to strings for JSON serialization
completion_trends = {str(k): v for k, v in completion_trends.items()}
return jsonify({
'productivity_by_day': productivity_by_day,
'priority_distribution': priority_distribution,
'completion_trends': completion_trends
})
@app.route('/')
def health_check():
"""Health check endpoint"""
return jsonify({
'status': 'healthy',
'message': 'Task Manager API is running',
'version': '1.0.0'
})
if __name__ == '__main__':
with app.app_context():
db.create_all()
print("🐍 Starting Flask backend...")
print("📊 Pandas loaded for analytics")
print("🗄️ Database ready")
app.run(debug=True, port=5000)
Database Seeding
backend/seed.py:Copy
from app import app, db, Task
from datetime import datetime, timedelta
import random
def seed_database():
"""Seed the database with sample tasks"""
with app.app_context():
# Clear existing data
Task.query.delete()
# Sample tasks
sample_tasks = [
{
'title': 'Complete project proposal',
'description': 'Write and submit the quarterly project proposal',
'status': 'todo',
'priority': 'high',
'due_date': datetime.now() + timedelta(days=3)
},
{
'title': 'Review code changes',
'description': 'Review pull requests from the development team',
'status': 'in_progress',
'priority': 'medium',
'due_date': datetime.now() + timedelta(days=1)
},
{
'title': 'Update documentation',
'description': 'Update API documentation with new endpoints',
'status': 'done',
'priority': 'low',
'due_date': datetime.now() - timedelta(days=2)
},
{
'title': 'Client meeting preparation',
'description': 'Prepare slides and demo for client presentation',
'status': 'todo',
'priority': 'high',
'due_date': datetime.now() + timedelta(days=7)
},
{
'title': 'Database backup',
'description': 'Perform weekly database backup and verification',
'status': 'todo',
'priority': 'medium',
'due_date': datetime.now() + timedelta(days=2)
}
]
for task_data in sample_tasks:
task = Task(**task_data)
db.session.add(task)
db.session.commit()
print(f"✅ Seeded database with {len(sample_tasks)} tasks")
if __name__ == '__main__':
seed_database()
Development Workflow
1. Install Dependencies
Copy
ppm install
- Install all JavaScript packages for the React frontend
- Create a Python virtual environment
- Install all Python packages for the Flask backend
- Set up the development environment
2. Database Setup
Copy
ppm run db:init
ppm run db:upgrade
ppm run db:seed
3. Start Development Servers
Copy
ppm run dev
- Flask backend on http://localhost:5000
- React frontend on http://localhost:3000
- Hot reloading for both services
4. Testing
Copy
# Run all tests
ppm run test
# Test individual parts
ppm run test:frontend
ppm run test:backend
5. Code Quality
Copy
# Lint all code
ppm run lint
# Format all code
ppm run format
Key Features Demonstrated
1. Unified Dependency Management
- Single
project.toml
manages both npm and pip packages - Consistent versioning across the entire stack
2. Cross-Language Data Flow
- React frontend consumes Flask API
- Python Pandas processes data for analytics
- TypeScript interfaces ensure type safety
3. Development Productivity
- One command to start both services
- Hot reloading in both frontend and backend
- Unified testing and linting
4. Real-World Patterns
- REST API design
- Database ORM with SQLAlchemy
- Modern React patterns with hooks and context
- Data analysis with Pandas
5. Production Ready
- Environment configuration
- Database migrations
- Error handling
- CORS configuration
Extending the Application
Add Real-time Features
Install Socket.IO for real-time updates:Copy
ppm add socket.io-client flask-socketio
Add Authentication
Copy
ppm add @auth0/auth0-react flask-jwt-extended
Add Data Visualization
Copy
ppm add recharts plotly
Add Testing
Copy
ppm add @testing-library/react @testing-library/jest-dom pytest-cov
Deployment
Build for Production
Copy
ppm run build
Docker Support
AddDockerfile
for containerization:
Copy
# Multi-stage build for both frontend and backend
FROM node:18 AS frontend
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm install
COPY frontend/ ./
RUN npm run build
FROM python:3.11 AS backend
WORKDIR /app
COPY backend/requirements.txt ./
RUN pip install -r requirements.txt
COPY backend/ ./
COPY --from=frontend /app/frontend/dist ./static
EXPOSE 5000
CMD ["python", "app.py"]
Performance Considerations
- Frontend: Code splitting, lazy loading, React Query for caching
- Backend: Database indexing, query optimization, caching with Redis
- Communication: Efficient API design, pagination, WebSocket for real-time features
This example demonstrates the full power of PPM for managing complex polyglot applications. The unified development workflow, combined with the strengths of both React and Python, creates a productive and maintainable development experience.