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

ppm init task-manager --template web
cd task-manager

2. Project Structure

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

[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:
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:
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:
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:
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:
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

ppm install
This command will:
  • 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

ppm run db:init
ppm run db:upgrade
ppm run db:seed

3. Start Development Servers

ppm run dev
This single command starts:

4. Testing

# Run all tests
ppm run test

# Test individual parts
ppm run test:frontend
ppm run test:backend

5. Code Quality

# 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:
ppm add socket.io-client flask-socketio

Add Authentication

ppm add @auth0/auth0-react flask-jwt-extended

Add Data Visualization

ppm add recharts plotly

Add Testing

ppm add @testing-library/react @testing-library/jest-dom pytest-cov

Deployment

Build for Production

ppm run build

Docker Support

Add Dockerfile for containerization:
# 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.