API Call Flow
Complete documentation of how API calls flow through the AquaGen application, from user interaction to UI update.
Overview
AquaGen follows a unidirectional data flow pattern:
Complete API Flow Diagram
1. GET Request Flow (Fetching Data)
2. POST Request Flow (Creating/Updating Data)
Code Examples
Example 1: GET Request (Fetching Dashboard Data)
Component Layer
// libs/dashboard/src/DashboardPage.jsx
import { useContext, useEffect } from 'react';
import { DashboardContext } from './store/DashboardStore';
function DashboardPage() {
const dashboardStore = useContext(DashboardContext);
useEffect(() => {
// Fetch data on component mount
dashboardStore.fetchDashboardData();
}, []);
if (dashboardStore.loading) {
return <Loader />;
}
if (dashboardStore.error) {
return <ErrorMessage error={dashboardStore.error} />;
}
return (
<div>
<TotalConsumption value={dashboardStore.data.total} />
<AlertsSummary alerts={dashboardStore.data.alerts} />
</div>
);
}
Store Layer
// libs/dashboard/src/store/DashboardStore.jsx
import { createContext, useState } from 'react';
import { DashboardController } from '../controller/DashboardController';
export const DashboardContext = createContext();
export function DashboardProvider({ children }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchDashboardData = async () => {
setLoading(true);
setError(null);
try {
const result = await DashboardController.fetchData();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const value = {
data,
loading,
error,
fetchDashboardData
};
return (
<DashboardContext.Provider value={value}>
{children}
</DashboardContext.Provider>
);
}
Controller Layer
// libs/dashboard/src/controller/DashboardController.js
import { DashboardDataSource } from '../dataSource/DashboardDataSource';
class DashboardController {
async fetchData() {
// 1. Get raw data
const rawData = await DashboardDataSource.getDashboardData();
// 2. Transform data for UI
const transformedData = {
total: rawData.totalConsumption || 0,
alerts: rawData.activeAlerts || [],
quality: rawData.waterQuality || 'Unknown',
chartData: this.formatChartData(rawData.chartData)
};
// 3. Apply business rules
if (transformedData.total < 0) {
throw new Error('Invalid consumption data');
}
return transformedData;
}
formatChartData(data) {
return data.map(item => ({
date: new Date(item.timestamp),
value: parseFloat(item.value),
label: item.label
}));
}
}
export default new DashboardController();
DataSource Layer
// libs/dashboard/src/dataSource/DashboardDataSource.js
import { apiClient } from '@aquagen-mf-webapp/shared/services';
class DashboardDataSource {
async getDashboardData() {
const response = await apiClient.get('/landingPage/userData', {
params: {
startDate: '2024-01-01',
endDate: '2024-01-31'
}
});
return response.data;
}
}
export default new DashboardDataSource();
Example 2: POST Request (Manual Entry)
Component Layer
// libs/manageAccount/src/components/ManualEntryForm.jsx
import { useContext, useState } from 'react';
import { ManualEntryContext } from '../store/ManualEntryStore';
function ManualEntryForm() {
const [formData, setFormData] = useState({
nodeId: '',
value: '',
timestamp: new Date()
});
const manualEntryStore = useContext(ManualEntryContext);
const handleSubmit = async (e) => {
e.preventDefault();
try {
await manualEntryStore.submitEntry(formData);
// Show success message
alert('Entry saved successfully!');
// Reset form
setFormData({
nodeId: '',
value: '',
timestamp: new Date()
});
} catch (error) {
alert('Error: ' + error.message);
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData.nodeId}
onChange={(e) => setFormData({...formData, nodeId: e.target.value})}
placeholder="Node ID"
/>
<input
type="number"
value={formData.value}
onChange={(e) => setFormData({...formData, value: e.target.value})}
placeholder="Value"
/>
<button type="submit" disabled={manualEntryStore.submitting}>
{manualEntryStore.submitting ? 'Saving...' : 'Save Entry'}
</button>
</form>
);
}
Store Layer
// libs/manageAccount/src/store/ManualEntryStore.jsx
import { createContext, useState } from 'react';
import { ManualEntryController } from '../controller/ManualEntryController';
export const ManualEntryContext = createContext();
export function ManualEntryProvider({ children }) {
const [entries, setEntries] = useState([]);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState(null);
const submitEntry = async (data) => {
setSubmitting(true);
setError(null);
try {
const result = await ManualEntryController.createEntry(data);
// Add to local state
setEntries([...entries, result]);
return result;
} catch (err) {
setError(err.message);
throw err;
} finally {
setSubmitting(false);
}
};
const value = {
entries,
submitting,
error,
submitEntry
};
return (
<ManualEntryContext.Provider value={value}>
{children}
</ManualEntryContext.Provider>
);
}
Controller Layer
// libs/manageAccount/src/controller/ManualEntryController.js
import { ManualEntryDataSource } from '../dataSource/ManualEntryDataSource';
class ManualEntryController {
async createEntry(data) {
// Validate
if (!data.nodeId || !data.value) {
throw new Error('Node ID and value are required');
}
// Format for API
const formattedData = {
node_id: data.nodeId,
measurement_value: parseFloat(data.value),
timestamp: data.timestamp.toISOString(),
unit: 'liters',
source: 'manual_entry'
};
// Send to backend
const response = await ManualEntryDataSource.postEntry(formattedData);
// Transform response
return {
id: response.id,
nodeId: response.node_id,
value: response.measurement_value,
timestamp: new Date(response.timestamp),
createdAt: new Date(response.created_at)
};
}
}
export default new ManualEntryController();
DataSource Layer
// libs/manageAccount/src/dataSource/ManualEntryDataSource.js
import { apiClient } from '@aquagen-mf-webapp/shared/services';
class ManualEntryDataSource {
async postEntry(data) {
const response = await apiClient.post('/manual/entry', data);
return response.data;
}
}
export default new ManualEntryDataSource();
API Client Configuration
Request Interceptor
// libs/shared/src/services/api/apiClient.js
// Add token and headers before request
axiosInstance.interceptors.request.use(
(config) => {
// Get token from session storage
const loginResponse = SessionStorage.getItem('loginResponse');
const token = loginResponse?.token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Add device fingerprint headers
config.headers['x-browser-name'] = browserInfo.name;
config.headers['x-browser-version'] = browserInfo.version;
config.headers['x-device-type'] = deviceType;
config.headers['x-os'] = osInfo;
config.headers['x-device-id'] = deviceFingerprint;
return config;
},
(error) => {
return Promise.reject(error);
}
);
Response Interceptor
// Handle responses and errors
axiosInstance.interceptors.response.use(
(response) => {
// Success - return data
return response;
},
async (error) => {
const status = error.response?.status;
switch (status) {
case 401:
// Unauthorized - redirect to login
window.location.href = '/login';
break;
case 420:
// Payment required - subscription expired
showSubscriptionExpiredDialog();
break;
case 440:
// Token expired - try to refresh
const refreshed = await refreshToken();
if (refreshed) {
// Retry original request
return axiosInstance.request(error.config);
} else {
window.location.href = '/login';
}
break;
case 500:
// Server error
console.error('Server error:', error);
break;
default:
break;
}
return Promise.reject(error);
}
);
Error Handling Flow
Loading States Flow
Real-Time Updates (WebSocket)
Best Practices
1. Always Use Try-Catch
const fetchData = async () => {
try {
const data = await apiClient.get('/endpoint');
setData(data);
} catch (error) {
setError(error.message);
}
};
2. Show Loading States
if (loading) return <Loader />;
if (error) return <ErrorMessage error={error} />;
return <DataDisplay data={data} />;
3. Validate Before Sending
// In Controller
if (!this.validateData(data)) {
throw new Error('Invalid data format');
}
4. Transform Data in Controller
// Controller transforms API response to UI format
const transformedData = {
displayDate: formatDate(rawData.timestamp),
displayValue: formatNumber(rawData.value),
...
};
5. Keep DataSource Thin
// DataSource only handles API calls
async getData() {
return await apiClient.get('/endpoint');
}
Next Steps
- Review API & Services for API client details
- Check State Management for store patterns
- See Authentication for token handling
Last Updated: February 2026