Skip to main content

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


Last Updated: February 2026