Axios Configuration
HTTP client configuration and API request utilities for the WorkPayCore frontend application with multi-instance setup, authentication, and EOR portal integration.
Overview
The axios configuration provides three specialized HTTP client instances with built-in authentication, EOR (Employer of Record) portal support, and automatic token refresh functionality.
HTTP Client Instances
httpV2 (Primary Instance)
The main HTTP client instance with full authentication and EOR portal integration.
Configuration:
import { httpV2 } from '@/api/axios';
// Base configuration
const httpV2 = axios.create({
baseURL: BASE_API_V2_ENDPOINT,
params: { ...defaultAuthAdminParams },
});
Default Parameters:
auth_employee_id: Employee ID for employee view contextauthorize_for: Authorization scopetoken: User authentication tokenauth_company_id: Company ID for multi-tenant operations
Use Cases:
- Primary API operations
- Authenticated requests
- EOR portal operations
- Employee management
- Company-specific data access
httpV3 (Bearer Token Instance)
HTTP client with Bearer token authentication for modern API endpoints.
Configuration:
import { httpV3 } from '@/api/axios';
// Bearer token configuration
const httpV3 = axios.create({
baseURL: BASE_API_V3_ENDPOINT,
headers: {
Authorization: `Bearer ${USER_TOKEN}`,
Accept: 'Application/json',
},
params: {
...defaultAuthAdminParams,
employee_id: isEmployeeView() ? currentUser()?.employee_id : null,
},
});
Use Cases:
- Modern REST API endpoints
- Bearer token authentication
- Employee-specific operations
- New API implementations
http (Basic Instance)
Simple HTTP client for unauthenticated or basic requests.
Configuration:
import { http } from '@/api/axios';
// Basic configuration
const http = axios.create({
baseURL: BASE_API_V1_ENDPOINT,
});
Use Cases:
- Public API endpoints
- Authentication requests
- Health checks
- Static data retrieval
Authentication System
Default Authentication Parameters
export const defaultAuthAdminParams = {
auth_employee_id: isEmployeeView()
? Number(CURRENT_AUTH_USER_ROLE?.employee_id) >= 1 &&
!CURRENT_AUTH_USER_ROLE?.is_onboarding
? CURRENT_AUTH_USER_ROLE?.employee_id
: null
: null,
authorize_for: null,
token: USER_TOKEN,
auth_company_id: AUTH_COMPANY_ID,
};
Token Management
// Automatic token refresh from response headers
httpV2.interceptors.response.use(
async response => {
const authHeader =
response?.headers?.authorization || response?.headers?.Authorization;
if (!isNull(authHeader)) {
await store.dispatch(
signUpRequestSuccess({
auth_token: authHeader,
}),
);
}
return response;
},
// Error handling...
);
Authentication Error Handling
// 401 Unauthorized handling
httpV2.interceptors.response.use(
response => response,
async error => {
if (error?.response?.status === 401) {
if (!isNull(USER_TOKEN)) {
useAuthStore.getState().setOpen(true);
useAuthStore.getState().setLastLoginEmail(currentUser()?.email);
}
}
return Promise.reject(error);
},
);
EOR Portal Integration
EOR Portal Parameters
export const getDefaultEorPortalParams = () => {
const company = getEorFilter('eor_company_single_select');
const payrollEorCompanyID = company?.value || undefined;
if (isEorPortalView()) {
return {
is_eor: 1,
auth_company_id: Number.isNaN(Number(payrollEorCompanyID))
? AUTH_COMPANY_ID
: payrollEorCompanyID,
};
}
return {};
};
EOR Request Interceptor
httpV2.interceptors.request.use(
config => {
if (!isEorPortalView()) {
return config; // Exit early if not in EOR portal context
}
const isExcludedUrl = EXCLUDED_EOR_ENDPOINTS?.some(
url => config.url?.includes(url),
);
if (isExcludedUrl) {
return config; // Exit early if URL is excluded
}
const eorParams = getDefaultEorPortalParams();
config.params = { ...config.params, ...eorParams };
// Handle different HTTP methods
const methodsRequiringBodyUpdate = new Set([
'put',
'post',
'delete',
'patch',
]);
if (methodsRequiringBodyUpdate.has(config.method?.toLowerCase())) {
if (config.data instanceof FormData) {
Object.entries(eorParams).forEach(([key, value]) => {
config.data.set(key, value);
});
} else if (config.data && typeof config.data === 'object') {
config.data = { ...config.data, ...eorParams };
}
}
return config;
},
error => Promise.reject(error),
);
Excluded EOR Endpoints
// Endpoints that bypass EOR parameter injection
const EXCLUDED_EOR_ENDPOINTS = [
'/auth',
'/public',
'/health',
// ... other excluded endpoints
];
Generic API Request Function
apiRequest Utility
const apiRequest = async <T = any,>({
method = 'GET',
url,
id,
payload,
query,
useLegacyAPI = false,
responseType,
}: {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
url: string;
id?: number | string;
payload?: any;
query?: Record<string, any>;
useLegacyAPI?: boolean;
responseType?: string;
}): Promise<T> => {
const requestUrl = id ? `${url}/${id}` : url;
const config: any = {
method,
url: requestUrl,
params: query,
data: payload,
useLegacyAPI,
responseType,
};
return httpV2.request(config);
};
Usage Examples
// GET request
const users = await apiRequest<User[]>({
url: '/users',
query: { page: 1, limit: 10 },
});
// POST request
const newUser = await apiRequest<User>({
method: 'POST',
url: '/users',
payload: { name: 'John Doe', email: 'john@example.com' },
});
// PUT request with ID
const updatedUser = await apiRequest<User>({
method: 'PUT',
url: '/users',
id: 123,
payload: { name: 'Jane Doe' },
});
// DELETE request
await apiRequest({
method: 'DELETE',
url: '/users',
id: 123,
});
// Legacy API request
const legacyData = await apiRequest({
url: '/legacy-endpoint',
useLegacyAPI: true,
});
Advanced Usage Examples
Employee Management API
import { httpV2 } from '@/api/axios';
class EmployeeAPI {
static async getEmployees(filters = {}) {
const response = await httpV2.get('/employees', {
params: filters,
});
return response.data;
}
static async createEmployee(employeeData) {
const response = await httpV2.post('/employees', employeeData);
return response.data;
}
static async updateEmployee(id, employeeData) {
const response = await httpV2.put(`/employees/${id}`, employeeData);
return response.data;
}
static async deleteEmployee(id) {
await httpV2.delete(`/employees/${id}`);
}
// Bulk operations
static async bulkUpdateEmployees(updates) {
const response = await httpV2.post('/employees/bulk-update', {
updates,
});
return response.data;
}
}
// Usage
const employees = await EmployeeAPI.getEmployees({
department: 'Engineering',
status: 'active',
});
EOR Portal Operations
import { httpV2 } from '@/api/axios';
import { isEorPortalView } from '@/utils/helpers';
class EorAPI {
static async getEorCompanies() {
// EOR parameters automatically injected by interceptor
const response = await httpV2.get('/eor/companies');
return response.data;
}
static async processPayroll(payrollData) {
if (!isEorPortalView()) {
throw new Error('EOR portal access required');
}
const response = await httpV2.post('/eor/payroll/process', payrollData);
return response.data;
}
static async getEmployeesByEorCompany(companyId) {
const response = await httpV2.get('/eor/employees', {
params: { eor_company_id: companyId },
});
return response.data;
}
}
File Upload with Authentication
import { httpV2 } from '@/api/axios';
const uploadFile = async (file, onProgress) => {
const formData = new FormData();
formData.append('file', file);
const response = await httpV2.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: progressEvent => {
const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total,
);
onProgress?.(progress);
},
});
return response.data;
};
Custom Hook for API Operations
import { useState, useCallback } from 'react';
import { httpV2 } from '@/api/axios';
const useAPI = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const request = useCallback(async config => {
setLoading(true);
setError(null);
try {
const response = await httpV2.request(config);
return response.data;
} catch (err) {
setError(err);
throw err;
} finally {
setLoading(false);
}
}, []);
const get = useCallback(
(url, params) => request({ method: 'GET', url, params }),
[request],
);
const post = useCallback(
(url, data) => request({ method: 'POST', url, data }),
[request],
);
const put = useCallback(
(url, data) => request({ method: 'PUT', url, data }),
[request],
);
const del = useCallback(url => request({ method: 'DELETE', url }), [request]);
return {
loading,
error,
get,
post,
put,
delete: del,
request,
};
};
// Usage in component
const EmployeeManager = () => {
const api = useAPI();
const handleCreateEmployee = async employeeData => {
try {
const newEmployee = await api.post('/employees', employeeData);
console.log('Created:', newEmployee);
} catch (error) {
console.error('Creation failed:', error);
}
};
return (
<div>
{api.loading && <div>Loading...</div>}
{api.error && <div>Error: {api.error.message}</div>}
{/* Component content */}
</div>
);
};
Configuration Constants
API Endpoints
// Base URLs from environment variables
export const BASE_API_V1_ENDPOINT = process.env.VITE_API_HOST;
export const BASE_API_V2_ENDPOINT = process.env.VITE_API_HOST_V2;
export const BASE_API_V3_ENDPOINT = process.env.VITE_API_HOST_V3;
export const LEGACY_HOST_ADDRESS = process.env.VITE_LEGACY_API_HOST;
Authentication Tokens
export const USER_TOKEN = userToken();
export const AUTH_COMPANY_ID = authCompanyID();
Error Handling
Global Error Interceptor
// Built into httpV2 instance
httpV2.interceptors.response.use(
response => response,
async error => {
// 401 Unauthorized - Show login modal
if (error?.response?.status === 401) {
if (!isNull(USER_TOKEN)) {
useAuthStore.getState().setOpen(true);
useAuthStore.getState().setLastLoginEmail(currentUser()?.email);
}
}
// Log errors for debugging
console.error('API Error:', {
url: error.config?.url,
method: error.config?.method,
status: error.response?.status,
data: error.response?.data,
});
return Promise.reject(error);
},
);
Custom Error Handling
import { httpV2 } from '@/api/axios';
const safeApiCall = async apiCall => {
try {
return await apiCall();
} catch (error) {
if (error.response?.status === 404) {
console.warn('Resource not found');
return null;
}
if (error.response?.status >= 500) {
console.error('Server error:', error.response.data);
throw new Error('Server error occurred');
}
throw error;
}
};
// Usage
const employee = await safeApiCall(() => httpV2.get(`/employees/${id}`));
Legacy API Support
Legacy Request Configuration
// Enable legacy API for specific requests
httpV2.interceptors.request.use(async config => {
if (config.useLegacyAPI) {
config.baseURL = LEGACY_HOST_ADDRESS;
}
return config;
});
// Usage
const legacyData = await httpV2.get('/legacy-endpoint', {
useLegacyAPI: true,
});
Best Practices
1. Use httpV2 for Standard Operations
// Preferred approach
import { httpV2 } from '@/api/axios';
const fetchData = () => httpV2.get('/endpoint');
2. Handle Authentication Gracefully
const authenticatedRequest = async requestFn => {
try {
return await requestFn();
} catch (error) {
if (error.response?.status === 401) {
// Token might be expired, let interceptor handle it
return null;
}
throw error;
}
};
3. Use TypeScript for Type Safety
interface ApiResponse<T> {
data: T;
message: string;
status: 'success' | 'error';
}
const getUser = async (id: number): Promise<User> => {
const response = await httpV2.get<ApiResponse<User>>(`/users/${id}`);
return response.data.data;
};
4. Implement Request Cancellation
const useApiWithCancellation = () => {
const cancelTokenRef = useRef<AbortController>();
const request = useCallback(async config => {
// Cancel previous request
if (cancelTokenRef.current) {
cancelTokenRef.current.abort();
}
// Create new abort controller
cancelTokenRef.current = new AbortController();
return httpV2.request({
...config,
signal: cancelTokenRef.current.signal,
});
}, []);
useEffect(() => {
return () => {
if (cancelTokenRef.current) {
cancelTokenRef.current.abort();
}
};
}, []);
return { request };
};
5. Cache API Responses
import { httpV2 } from '@/api/axios';
class CachedAPI {
private static cache = new Map();
static async get(url, params = {}, cacheDuration = 5 * 60 * 1000) {
const cacheKey = `${url}${JSON.stringify(params)}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < cacheDuration) {
return cached.data;
}
const response = await httpV2.get(url, { params });
this.cache.set(cacheKey, {
data: response.data,
timestamp: Date.now(),
});
return response.data;
}
static clearCache() {
this.cache.clear();
}
}
Performance Considerations
- Request Deduplication: Prevent duplicate requests for the same endpoint
- Response Caching: Cache frequently accessed data
- Request Cancellation: Cancel ongoing requests when components unmount
- Batch Operations: Group multiple operations into single requests
- Pagination: Implement proper pagination for large datasets
Related Utilities
- EOR Helpers - For EOR portal filter management
- General Helpers - For authentication utilities
- String Utilities - For URL construction
TypeScript Definitions
interface ApiRequestConfig {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
url: string;
id?: number | string;
payload?: any;
query?: Record<string, any>;
useLegacyAPI?: boolean;
responseType?: string;
}
export const http: AxiosInstance;
export const httpV2: AxiosInstance;
export const httpV3: AxiosInstance;
export function apiRequest<T = any>(config: ApiRequestConfig): Promise<T>;
export const defaultAuthAdminParams: Record<string, any>;
export function getDefaultEorPortalParams(): Record<string, any>;
Environment Variables
# API Endpoints
VITE_API_HOST=https://api.workpay.co.ke/api/v1
VITE_API_HOST_V2=https://api.workpay.co.ke/api/v2
VITE_API_HOST_V3=https://api.workpay.co.ke/api/v3
VITE_LEGACY_API_HOST=https://legacy.workpay.co.ke/api
# Authentication
VITE_AUTH_TOKEN_KEY=workpay_auth_token
Dependencies
- axios: HTTP client library
- Redux Toolkit: State management for authentication
- EOR Helpers: For portal-specific filtering
- Authentication Store: For login state management