Day 3: React Frontend Setup - Building Your First Distributed System Interface
The Big Picture: Why Frontend Architecture Matters in Distributed Systems
Imagine Netflix's homepage loading instantly for millions of users worldwide, or WhatsApp delivering messages in real-time across continents. These experiences aren't just "websites" – they're sophisticated distributed systems where the frontend serves as the critical entry point that orchestrates countless backend services.
Today, you're building the user-facing gateway of your distributed system. Think of it as constructing the control tower of an airport – it needs to coordinate with multiple ground systems (your backend services) while providing pilots (users) with a clean, responsive interface to navigate complex operations.
Core Concepts: The Frontend's Role in System Design
Your React frontend isn't just displaying data; it's the orchestrator of user experience in a distributed architecture. In real-world systems like Uber, the mobile app coordinates with dozens of microservices: location services, payment processing, driver matching, and real-time tracking. Each service runs independently, but the frontend creates the illusion of a single, cohesive experience.
Modern React applications implement several distributed system patterns at the frontend level: client-side routing mimics service discovery, state management libraries like Redux implement eventual consistency patterns, and component lazy loading follows the same principles as microservice scaling.
Implementation Deep Dive
Project Architecture Overview
Your React TypeScript setup follows enterprise patterns used by companies like Airbnb and Facebook. TypeScript provides compile-time safety crucial for large distributed systems, while Material-UI ensures consistent design language across your service ecosystem.
Step-by-Step Implementation
Let's build your frontend layer step by step, creating a foundation that will integrate seamlessly with your Flask backend from Day 2.
Hands-On Project: E-Commerce Product Dashboard
Source code Repository : https://github.com/sysdr/infrawatch
You'll create a product management dashboard that demonstrates real distributed system patterns: component isolation (microservice analogy), centralized state management (distributed data consistency), and API integration (service communication).
Project Structure Setup
# Create project directory structure
mkdir ecommerce-frontend
cd ecommerce-frontend
# Initialize React TypeScript project
npx create-react-app . --template typescript
# Install core dependencies
npm install @mui/material @emotion/react @emotion/styled
npm install @mui/icons-material
npm install react-router-dom @types/react-router-dom
npm install axios
npm install @testing-library/jest-dom @testing-library/react @testing-library/user-event
# Development dependencies
npm install --save-dev prettier eslint-config-prettier
Core Application Structure
Create the following directory structure:
src/
├── components/
│ ├── common/
│ │ ├── Header.tsx
│ │ └── Navigation.tsx
│ ├── products/
│ │ ├── ProductList.tsx
│ │ ├── ProductCard.tsx
│ │ └── ProductForm.tsx
│ └── layout/
│ └── Dashboard.tsx
├── services/
│ └── api.ts
├── types/
│ └── Product.ts
├── utils/
│ └── constants.ts
└── App.tsx
Implementation Files
src/types/Product.ts
export interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
inStock: boolean;
createdAt: string;
}
export interface ProductFormData {
name: string;
price: number;
description: string;
category: string;
inStock: boolean;
}
src/services/api.ts
import axios from 'axios';
import { Product, ProductFormData } from '../types/Product';
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:5000';
const api = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
export const productService = {
getAllProducts: async (): Promise<Product[]> => {
const response = await api.get('/api/products');
return response.data;
},
getProduct: async (id: number): Promise<Product> => {
const response = await api.get(`/api/products/${id}`);
return response.data;
},
createProduct: async (product: ProductFormData): Promise<Product> => {
const response = await api.post('/api/products', product);
return response.data;
},
updateProduct: async (id: number, product: ProductFormData): Promise<Product> => {
const response = await api.put(`/api/products/${id}`, product);
return response.data;
},
deleteProduct: async (id: number): Promise<void> => {
await api.delete(`/api/products/${id}`);
},
};
src/components/products/ProductCard.tsx
import React from 'react';
import {
Card,
CardContent,
CardActions,
Typography,
Button,
Chip,
Box,
} from '@mui/material';
import { Product } from '../../types/Product';
interface ProductCardProps {
product: Product;
onEdit: (product: Product) => void;
onDelete: (id: number) => void;
}
const ProductCard: React.FC<ProductCardProps> = ({ product, onEdit, onDelete }) => {
return (
<Card sx={{ maxWidth: 345, margin: 1 }}>
<CardContent>
<Typography gutterBottom variant="h5" component="div">
{product.name}
</Typography>
<Typography variant="body2" color="text.secondary">
{product.description}
</Typography>
<Box sx={{ mt: 2, mb: 1 }}>
<Typography variant="h6" color="primary">
${product.price.toFixed(2)}
</Typography>
<Chip
label={product.category}
variant="outlined"
size="small"
sx={{ mr: 1 }}
/>
<Chip
label={product.inStock ? 'In Stock' : 'Out of Stock'}
color={product.inStock ? 'success' : 'error'}
size="small"
/>
</Box>
</CardContent>
<CardActions>
<Button size="small" onClick={() => onEdit(product)}>
Edit
</Button>
<Button size="small" color="error" onClick={() => onDelete(product.id)}>
Delete
</Button>
</CardActions>
</Card>
);
};
export default ProductCard;
src/components/products/ProductList.tsx
import React, { useState, useEffect } from 'react';
import {
Grid,
Typography,
Button,
Container,
Alert,
CircularProgress,
Box,
} from '@mui/material';
import { Add as AddIcon } from '@mui/icons-material';
import ProductCard from './ProductCard';
import ProductForm from './ProductForm';
import { Product, ProductFormData } from '../../types/Product';
import { productService } from '../../services/api';
const ProductList: React.FC = () => {
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [showForm, setShowForm] = useState(false);
const [editingProduct, setEditingProduct] = useState<Product | null>(null);
useEffect(() => {
loadProducts();
}, []);
const loadProducts = async () => {
try {
setLoading(true);
const data = await productService.getAllProducts();
setProducts(data);
setError(null);
} catch (err) {
setError('Failed to load products. Make sure your backend is running.');
// Mock data for development when backend is not available
setProducts([
{
id: 1,
name: 'Sample Product',
price: 29.99,
description: 'This is a sample product for development',
category: 'Electronics',
inStock: true,
createdAt: new Date().toISOString(),
},
]);
} finally {
setLoading(false);
}
};
const handleSaveProduct = async (productData: ProductFormData) => {
try {
if (editingProduct) {
await productService.updateProduct(editingProduct.id, productData);
} else {
await productService.createProduct(productData);
}
await loadProducts();
setShowForm(false);
setEditingProduct(null);
} catch (err) {
setError('Failed to save product');
}
};
const handleEditProduct = (product: Product) => {
setEditingProduct(product);
setShowForm(true);
};
const handleDeleteProduct = async (id: number) => {
try {
await productService.deleteProduct(id);
await loadProducts();
} catch (err) {
setError('Failed to delete product');
}
};
if (loading) {
return (
<Box display="flex" justifyContent="center" mt={4}>
<CircularProgress />
</Box>
);
}
return (
<Container>
<Box sx={{ my: 4 }}>
<Typography variant="h4" component="h1" gutterBottom>
Product Management Dashboard
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => setShowForm(true)}
sx={{ mb: 3 }}
>
Add Product
</Button>
<Grid container spacing={3}>
{products.map((product) => (
<Grid item xs={12} sm={6} md={4} key={product.id}>
<ProductCard
product={product}
onEdit={handleEditProduct}
onDelete={handleDeleteProduct}
/>
</Grid>
))}
</Grid>
<ProductForm
open={showForm}
product={editingProduct}
onSave={handleSaveProduct}
onClose={() => {
setShowForm(false);
setEditingProduct(null);
}}
/>
</Box>
</Container>
);
};
export default ProductList;
src/components/products/ProductForm.tsx
import React, { useState, useEffect } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
Button,
FormControlLabel,
Switch,
MenuItem,
} from '@mui/material';
import { Product, ProductFormData } from '../../types/Product';
interface ProductFormProps {
open: boolean;
product: Product | null;
onSave: (product: ProductFormData) => void;
onClose: () => void;
}
const categories = ['Electronics', 'Clothing', 'Books', 'Home', 'Sports'];
const ProductForm: React.FC<ProductFormProps> = ({ open, product, onSave, onClose }) => {
const [formData, setFormData] = useState<ProductFormData>({
name: '',
price: 0,
description: '',
category: 'Electronics',
inStock: true,
});
useEffect(() => {
if (product) {
setFormData({
name: product.name,
price: product.price,
description: product.description,
category: product.category,
inStock: product.inStock,
});
} else {
setFormData({
name: '',
price: 0,
description: '',
category: 'Electronics',
inStock: true,
});
}
}, [product, open]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSave(formData);
};
return (
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
<form onSubmit={handleSubmit}>
<DialogTitle>
{product ? 'Edit Product' : 'Add New Product'}
</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
label="Product Name"
fullWidth
variant="outlined"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
sx={{ mb: 2 }}
/>
<TextField
margin="dense"
label="Price"
type="number"
fullWidth
variant="outlined"
value={formData.price}
onChange={(e) => setFormData({ ...formData, price: parseFloat(e.target.value) })}
required
sx={{ mb: 2 }}
/>
<TextField
margin="dense"
label="Description"
fullWidth
multiline
rows={3}
variant="outlined"
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
sx={{ mb: 2 }}
/>
<TextField
select
margin="dense"
label="Category"
fullWidth
variant="outlined"
value={formData.category}
onChange={(e) => setFormData({ ...formData, category: e.target.value })}
sx={{ mb: 2 }}
>
{categories.map((category) => (
<MenuItem key={category} value={category}>
{category}
</MenuItem>
))}
</TextField>
<FormControlLabel
control={
<Switch
checked={formData.inStock}
onChange={(e) => setFormData({ ...formData, inStock: e.target.checked })}
/>
}
label="In Stock"
/>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button type="submit" variant="contained">
{product ? 'Update' : 'Create'}
</Button>
</DialogActions>
</form>
</Dialog>
);
};
export default ProductForm;
src/App.tsx
import React from 'react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import ProductList from './components/products/ProductList';
const theme = createTheme({
palette: {
mode: 'light',
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<ProductList />
</ThemeProvider>
);
}
export default App;
src/setupTests.ts
import '@testing-library/jest-dom';
package.json scripts update
{
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"test:coverage": "react-scripts test --coverage --watchAll=false",
"docker:build": "docker build -t ecommerce-frontend .",
"docker:run": "docker run -p 3000:3000 ecommerce-frontend"
}
}
Docker Configuration
Dockerfile
# Build stage
FROM node:18-alpine as build
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Production stage
FROM nginx:alpine
# Copy built application
COPY --from=build /app/build /usr/share/nginx/html
# Copy custom nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
nginx.conf
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x__forwarded_for;
}
}
docker-compose.yml
version: '3.8'
services:
frontend:
build: .
ports:
- "3000:80"
environment:
- REACT_APP_API_URL=http://localhost:5000
depends_on:
- backend
networks:
- app-network
backend:
build: ../backend
ports:
- "5000:5000"
networks:
- app-network
networks:
app-network:
driver: bridge
Testing Implementation
src/components/products/tests/ProductCard.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import ProductCard from '../ProductCard';
import { Product } from '../../../types/Product';
const mockProduct: Product = {
id: 1,
name: 'Test Product',
price: 29.99,
description: 'Test description',
category: 'Electronics',
inStock: true,
createdAt: '2024-01-01T00:00:00Z',
};
const mockOnEdit = jest.fn();
const mockOnDelete = jest.fn();
describe('ProductCard', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('renders product information correctly', () => {
render(
<ProductCard
product={mockProduct}
onEdit={mockOnEdit}
onDelete={mockOnDelete}
/>
);
expect(screen.getByText('Test Product')).toBeInTheDocument();
expect(screen.getByText('Test description')).toBeInTheDocument();
expect(screen.getByText('$29.99')).toBeInTheDocument();
expect(screen.getByText('Electronics')).toBeInTheDocument();
expect(screen.getByText('In Stock')).toBeInTheDocument();
});
test('calls onEdit when edit button is clicked', () => {
render(
<ProductCard
product={mockProduct}
onEdit={mockOnEdit}
onDelete={mockOnDelete}
/>
);
fireEvent.click(screen.getByText('Edit'));
expect(mockOnEdit).toHaveBeenCalledWith(mockProduct);
});
test('calls onDelete when delete button is clicked', () => {
render(
<ProductCard
product={mockProduct}
onEdit={mockOnEdit}
onDelete={mockOnDelete}
/>
);
fireEvent.click(screen.getByText('Delete'));
expect(mockOnDelete).toHaveBeenCalledWith(1);
});
});
Build and Test Commands
Local Development
# Start development server
npm start
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build for production
npm run build
Docker Commands
# Build Docker image
docker build -t ecommerce-frontend .
# Run container
docker run -p 3000:80 ecommerce-frontend
# Run with docker-compose
docker-compose up --build
Expected Output Verification
After running npm start, you should see:
Development server starts on http://localhost:3000
Product Management Dashboard displays with "Add Product" button
Sample product card shows (if backend is not connected)
Clicking "Add Product" opens a modal form
Form includes all required fields with validation
For Docker build:
docker buildcompletes without errorsdocker runstarts container successfullyApplication accessible at http://localhost:3000
For tests:
npm testpasses all test suitesCoverage report shows adequate test coverage
No compilation errors or warnings
Assignment: Enhanced Product Categories
Your homework is to extend the application with a category management system. Add a new component CategoryManager that allows users to create, edit, and delete product categories dynamically. The category dropdown in the product form should populate from this dynamic list instead of the hardcoded array.
Requirements:
Create
CategoryManagercomponent with add/edit/delete functionalityImplement category API service methods
Add category management to the main dashboard
Update ProductForm to use dynamic categories
Add tests for new components
Ensure Docker deployment works with new features
This assignment reinforces the distributed system concept of service separation, where category management becomes its own microservice concern while maintaining loose coupling with product management.
Context in Distributed Systems
Your React frontend implements several distributed system patterns that you'll recognize in enterprise applications. Component isolation mirrors microservice architecture, where each component has specific responsibilities and minimal dependencies. The API service layer demonstrates the Gateway pattern, centralizing external communication and providing fallback mechanisms for service failures.
State management in React follows eventual consistency principles, similar to distributed databases. Your loading states and error handling prepare users for the reality of distributed systems, where network latency and service unavailability are normal operating conditions, not exceptional cases.
What You've Accomplished
Today you've built a production-ready frontend architecture that demonstrates enterprise-level patterns used by companies like Netflix, Uber, and Spotify. Your component structure supports scaling to hundreds of developers working on different features simultaneously, while your API abstraction layer prepares you for the microservice architectures you'll encounter in real-world distributed systems.
This foundation will serve as your user interface gateway as we continue building backend services, implementing authentication, adding real-time features, and deploying to cloud infrastructure in the coming weeks.

