Backend-Frontend Overview
Understand how the React frontend and Spring Boot backend work together, divide responsibilities, and exchange data.
Architecture Overview
Smart Supply Pro follows the modern SPA + REST API pattern:
(Browser)"] subgraph Frontend["π± Frontend (React/TypeScript)"] Router["π£οΈ React Router
(Client-side routing)"] Pages["π Page Components
(Dashboard, Suppliers, etc.)"] State["πΎ React Context/State
(Auth, Notifications, Filters)"] APILayer["π API Client
(httpClient.ts)"] end subgraph Network["π HTTPS + Session Cookies"] direction LR Nginx["βοΈ Nginx
(Reverse Proxy)"] end subgraph Backend["βοΈ Backend (Spring Boot)"] Controllers["π― Controllers
(REST endpoints)"] Services["πΌ Services
(Business logic)"] Repos["ποΈ Repositories
(Data access)"] Security["π Security
(OAuth2, @PreAuthorize)"] end Database["ποΈ Oracle DB
(Data store)"] OAuth2["π Google OAuth2
(Authentication)"] User -->|Browser| Router Router --> Pages Pages --> State Pages --> APILayer APILayer -->|REST + Cookies| Nginx Nginx -->|Localhost| Controllers Controllers --> Services Services --> Repos Repos --> Database Controllers --> Security Security --> OAuth2 style Frontend fill:#61dafb,stroke:#0288d1,stroke-width:2px,color:#000 style Backend fill:#6db33f,stroke:#558b2f,stroke-width:2px,color:#fff style Nginx fill:#009639,stroke:#006e2c,stroke-width:2px,color:#fff style Database fill:#f80000,stroke:#b71c1c,stroke-width:2px,color:#fff style OAuth2 fill:#4285f4,stroke:#1565c0,stroke-width:2px,color:#fff
Responsibility Division
Frontend
(React/TypeScript in /frontend)
What the frontend handles:
| Responsibility | Example | Technology |
|---|---|---|
| User Interface | Rendering forms, buttons, tables | React components |
| Client-side Routing | Navigation between pages | React Router |
| State Management | Auth context, notification queue, filter state | React Context / hooks |
| API Calls | Calling /api/suppliers,
/api/inventory |
axios via httpClient.ts |
| Input Validation | Form field error hints (client-side only) | HTML5 constraints + JS |
| Error Display | Toast notifications, error messages | React toast library |
| Demo Mode Detection | Read-only UI if demoReadonly=true |
localStorage check |
What the frontend does NOT do:
- β Business logic (calculations, rules, constraints)
- β Database access (all data flows through backend API)
- β Authentication (backend handles OAuth2, frontend detects session)
- β Authorization (frontend hides UI; backend enforces via
@PreAuthorize) - β Data validation (client-side hints only; server is source of truth)
Backend (Spring Boot
in /src/main/java)
What the backend handles:
| Responsibility | Example | Technology |
|---|---|---|
| REST API | /api/suppliers, /api/inventory
endpoints |
Spring @RestController |
| Business Logic | Calculate stock levels, validate constraints | Spring @Service |
| Data Access | Query/insert/update to Oracle DB | Spring Data JPA |
| Authentication | OAuth2 exchange, session creation | Spring Security OAuth2 |
| Authorization | @PreAuthorize("hasRole('ADMIN')") |
Spring Security annotations |
| Data Validation | JSR-380 constraints, custom validators | @Valid + validators |
| Error Responses | ErrorResponse with status code + message |
GlobalExceptionHandler |
| Transactions | ACID guarantees across updates | @Transactional |
What the backend does NOT do:
- β Render HTML (API-only, no templates)
- β Client-side routing (frontend handles that)
- β Store user session state in memory (uses cookies, Spring Security handles it)
- β User interface logic (buttons, forms, dialogs)
Data Flow: User Interaction
Example: User Views Suppliers
User clicks "Suppliers" in nav
β
Frontend Router navigates to /suppliers
β
Page component mounts, calls httpClient.get('/suppliers')
β
[CORS check] Nginx proxy allows (same-origin)
β
Backend SupplierController.listAll() receives request
β
@PreAuthorize checks if user is authenticated OR demo.readonly is true
β
SupplierService.findAll() queries SupplierRepository
β
JPA executes SQL: SELECT * FROM suppliers
β
Oracle returns rows, JPA maps to Supplier entities
β
Mapper converts entities β SupplierDTOs
β
SupplierController returns ResponseEntity<List<SupplierDTO>>
β
Spring converts to JSON: [{ id, name, email, phone }, ...]
β
Response returns to frontend (with session cookie in Set-Cookie header)
β
Frontend axios interceptor checks for 401 β no, success
β
Frontend setState(suppliers) β React re-renders table
β
User sees supplier list on screen
Example: User Creates New Supplier
User fills form: name="Acme Co", email="contact@acme.com"
β
User clicks "Save"
β
Frontend validates required fields (client-side hint)
β
Frontend calls httpClient.post('/suppliers', { name, email, ... })
β
Backend SupplierController.create(SupplierDTO supplierDTO)
β
@PreAuthorize("hasRole('ADMIN')") checks authorization
β
SupplierService receives DTO
β
Service validates constraints (JSR-380)
β
Service checks for duplicate name using repository
β
Service creates Supplier entity, saves to DB
β
Service returns success response with new supplier
β
Frontend receives 201 Created + SupplierDTO
β
Frontend updates state, shows toast "Supplier created!"
β
Frontend navigates to supplier detail page
Key Design Patterns
1. DTOs as API Contracts
Problem: Entities have internal fields (JPA relations, hibernateLazyInitializationException, password hashes, etc.). Sending them directly is a security/performance risk.
Solution: DTOs (Data Transfer Objects) represent what the API actually returns to clients.
Example:
// Entity (database model)
@Entity
public class Supplier {
@Id
private String id;
private String name;
private String email;
private LocalDateTime createdAt;
// ... 10 other internal fields
@OneToMany(mappedBy = "supplier")
private List<InventoryItem> items; // lazy-loaded
}
// DTO (API contract)
public record SupplierDTO(
String id,
String name,
String email
) {}
// Controller
@GetMapping
public ResponseEntity<List<SupplierDTO>> listAll() {
return ResponseEntity.ok(
supplierService.findAll()
.stream()
.map(mapperService::toDTO)
.toList()
);
}Frontend consumes:
const response = await httpClient.get('/suppliers');
// response.data = [
// { id: "S1", name: "Acme", email: "..." },
// { id: "S2", name: "Globex", email: "..." }
// ]2. Centralized HTTP Client
Problem: If you scatter API calls across 20 components, how do you handle auth timeouts, retries, error handling consistently?
Solution: Centralize in
httpClient.ts
// src/api/httpClient.ts
const httpClient = axios.create({
baseURL: '/api',
withCredentials: true, // include session cookie
timeout: 30_000,
});
// Global 401 handler
httpClient.interceptors.response.use(
(res) => res,
(error) => {
if (error.response?.status === 401) {
// redirect to /login
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default httpClient;Usage in components:
const SuppliersPage = () => {
const [suppliers, setSuppliers] = useState([]);
useEffect(() => {
httpClient.get('/suppliers')
.then(res => setSuppliers(res.data))
.catch(error => toast.error(error.response?.data?.message));
}, []);
return <SupplierTable suppliers={suppliers} />;
};3. @PreAuthorize for Role-Based Access
Problem: If the UI hides an βEditβ button for non-admins, a malicious user could still call the API directly.
Solution: Backend enforces authorization on every endpoint.
@RestController
@RequestMapping("/api/suppliers")
public class SupplierController {
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping
public ResponseEntity<List<SupplierDTO>> listAll() {
// Authenticated users OR demo-readonly mode can read
return ResponseEntity.ok(supplierService.findAll());
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping
public ResponseEntity<SupplierDTO> create(@Valid @RequestBody SupplierDTO dto) {
// Only admins can create
return ResponseEntity.status(HttpStatus.CREATED)
.body(supplierService.save(dto));
}
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable String id) {
// Only admins can delete
supplierService.deleteById(id);
return ResponseEntity.noContent().build();
}
}Frontend respects this:
const canEditSupplier = user?.role === 'ADMIN';
return (
<button
disabled={!canEditSupplier}
onClick={() => updateSupplier(id, data)}
>
Edit
</button>
);But backend doesnβt trust the frontend:
If user sends POST /api/suppliers without ADMIN role:
Backend returns 403 Forbidden
Frontend catches 401/403 and shows "Access Denied"
4. Error Responses Are Standardized
Problem: Different endpoints throw different exceptions (DuplicateResourceException, InvalidRequestException, etc.). Frontend has no idea what to display.
Solution: GlobalExceptionHandler maps all
exceptions to a standard ErrorResponse shape.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(DuplicateResourceException.class)
public ResponseEntity<ErrorResponse> handleDuplicate(DuplicateResourceException e) {
return ErrorResponse.builder()
.status(HttpStatus.CONFLICT)
.message(e.getMessage())
.build()
.toResponseEntity();
}
@ExceptionHandler(InvalidRequestException.class)
public ResponseEntity<ErrorResponse> handleInvalid(InvalidRequestException e) {
return ErrorResponse.builder()
.status(HttpStatus.BAD_REQUEST)
.message(e.getMessage())
.build()
.toResponseEntity();
}
}Response format:
{
"error": "conflict",
"message": "Supplier 'Acme Co' already exists",
"timestamp": "2025-11-20T10:30:00.123Z",
"correlationId": "SSP-1700551800123-7891"
}Frontend handles consistently:
httpClient.post('/suppliers', formData)
.catch(error => {
const { message, error: errorType } = error.response?.data;
if (errorType === 'conflict') {
toast.error(`Duplicate: ${message}`);
} else if (errorType === 'bad_request') {
setFormErrors(parseValidationErrors(message));
} else {
toast.error('Server error occurred');
}
});Technology Stack
Frontend
- Framework: React 18+
- Language: TypeScript
- HTTP Client: axios (wrapped in
httpClient.ts) - Routing: React Router v6+
- State: React Context + hooks
- Build Tool: Vite
- Testing: React Testing Library, Vitest, Cypress (if applicable)
- UI Components: (Material-UI, custom, or other)
Backend
- Framework: Spring Boot 3.x+
- Language: Java 17+
- REST: Spring Web MVC
- Database: Spring Data JPA + Hibernate
- Authentication: Spring Security OAuth2
- Validation: JSR-380 (Jakarta Validation)
- Data Mapping: Custom mappers or MapStruct
- Testing: JUnit5, Mockito, TestContainers
- Build: Maven
- Logging: SLF4J + Logback
File Organization
Backend
Source
(/src/main/java/com/smartsupplypro/inventory)
controller/ β REST endpoints (@RestController)
βββ AuthController.java
βββ SupplierController.java
βββ InventoryItemController.java
βββ StockHistoryController.java
βββ AnalyticsController.java
dto/ β API contracts (request/response shapes)
βββ SupplierDTO.java
βββ InventoryItemDTO.java
βββ StockHistoryDTO.java
βββ ...
service/ β Business logic (transactions, rules)
βββ SupplierService.java
βββ InventoryItemService.java
βββ ...
repository/ β Data access (JPA queries)
βββ SupplierRepository.java
βββ InventoryItemRepository.java
βββ ...
security/ β OAuth2, role/permission logic
βββ SecurityConfig.java
exception/ β Exception handling
βββ GlobalExceptionHandler.java
βββ dto/
βββ ErrorResponse.java
mapper/ β Entity β DTO conversion
βββ DtoMappers.java
Frontend Source
(/frontend/src)
api/ β HTTP client & API methods
βββ httpClient.ts β Centralized axios instance
βββ inventory/ β Domain-specific API calls
βββ analytics/
βββ ...
pages/ β Page-level components (routes)
βββ DashboardPage.tsx
βββ SuppliersPage.tsx
βββ InventoryPage.tsx
βββ LoginPage.tsx
components/ β Reusable UI components
βββ SupplierForm.tsx
βββ InventoryTable.tsx
βββ ...
context/ β Global state (Auth, notifications)
βββ AuthContext.tsx
βββ NotificationContext.tsx
βββ ...
features/ β Feature-specific logic
βββ auth/ β Auth flows, login/logout
Summary: How They Work Together
- User loads the app β Frontend loads, calls
GET /api/auth/me - Backend responds β Either user profile (logged in) or 401 (not logged in)
- Frontend detects auth state β Renders dashboard or redirects to login
- User navigates β Frontend React Router handles (no page reload)
- User clicks action (e.g., βCreate Supplierβ)
- Frontend calls API β
POST /api/supplierswith DTO JSON body - Backend processes β Validates, applies business logic, persists to DB
- Backend responds β 201 Created + new SupplierDTO (or error)
- Frontend updates β React state changes, UI re-renders
- User sees result β Toast notification, updated table, etc.
Navigation
- β Back to Integration Index
- β API Contract & Endpoints - Endpoint catalog
- β Authentication Flow - Login/logout details
- β Error Handling Contract - Error shapes and mapping
- β Back to Architecture Index