frontend/src/services/api.js
Base URL: http://localhost:5000/api/v1
Timeout: 15 seconds
Content-Type: application/json
Credentials: included (cookies)// Token is automatically added to all requests
const token = localStorage.getItem('token');
config.headers.Authorization = `Bearer ${token}`;import { setAuthToken, removeAuthToken, getAuthToken, isAuthenticated } from '@/services/api';
// Set token after login
setAuthToken('your-jwt-token-here');
// Get current token
const token = getAuthToken();
// Check if authenticated
if (isAuthenticated()) {
// User is logged in
}
// Remove token (logout)
removeAuthToken();- Automatically adds JWT token to headers
- Checks for token existence before adding
🚀 [API Request] GET http://localhost:5000/api/v1/students
- Adds timestamp for response time calculation
- Useful for debugging and performance monitoring
✅ [API Response] GET /students - 245ms
| Status Code | Error Type | Behavior |
|---|---|---|
| 400 | VALIDATION_ERROR | Returns validation errors |
| 401 | AUTH_ERROR | Clears token & redirects to login |
| 403 | FORBIDDEN_ERROR | Shows permission denied |
| 404 | NOT_FOUND_ERROR | Resource not found |
| 409 | CONFLICT_ERROR | Duplicate entry |
| 422 | VALIDATION_ERROR | Returns field errors |
| 429 | RATE_LIMIT_ERROR | Too many requests |
| 500 | SERVER_ERROR | Internal server error |
| 502 | SERVER_ERROR | Bad gateway |
| 503 | SERVER_ERROR | Service unavailable |
{
message: 'Network error. Please check your connection.',
type: 'NETWORK_ERROR'
}import api from '@/services/api';
const fetchStudents = async () => {
try {
const response = await api.get('/students');
return response.data;
} catch (error) {
console.error('Failed to fetch students:', error.message);
}
};const createStudent = async (studentData) => {
try {
const response = await api.post('/students', studentData);
return response.data;
} catch (error) {
if (error.type === 'VALIDATION_ERROR') {
console.error('Validation errors:', error.errors);
}
throw error;
}
};const updateStudent = async (id, data) => {
try {
const response = await api.put(`/students/${id}`, data);
return response.data;
} catch (error) {
console.error('Update failed:', error.message);
throw error;
}
};const deleteStudent = async (id) => {
try {
const response = await api.delete(`/students/${id}`);
return response.data;
} catch (error) {
console.error('Delete failed:', error.message);
throw error;
}
};const fetchStudentsWithFilters = async (page = 1, limit = 10, search = '') => {
try {
const response = await api.get('/students', {
params: {
page,
limit,
search,
}
});
return response.data;
} catch (error) {
console.error('Fetch failed:', error.message);
throw error;
}
};const uploadFile = async (formData) => {
try {
const response = await api.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
timeout: 60000, // 60 seconds for file uploads
});
return response.data;
} catch (error) {
console.error('Upload failed:', error.message);
throw error;
}
};try {
const response = await api.get('/students');
// Success handling
} catch (error) {
if (error.type === 'AUTH_ERROR') {
// Redirect to login
} else if (error.type === 'VALIDATION_ERROR') {
// Show validation errors
} else {
// Show generic error
}
}api.get('/students')
.then(response => {
// Success handling
})
.catch(error => {
console.error('Error:', error.message);
console.error('Type:', error.type);
});const handleApiError = (error) => {
switch (error.type) {
case 'AUTH_ERROR':
showToast('Session expired. Please login again.', 'error');
break;
case 'VALIDATION_ERROR':
showValidationErrors(error.errors);
break;
case 'NETWORK_ERROR':
showToast('No internet connection.', 'error');
break;
case 'SERVER_ERROR':
showToast('Server error. Please try again later.', 'error');
break;
default:
showToast(error.message, 'error');
}
};const response = await api.get('/students', {
headers: {
'X-Custom-Header': 'custom-value',
}
});const response = await api.post('/export', data, {
timeout: 60000, // 60 seconds
});const downloadFile = async (url, filename) => {
try {
const response = await api.get(url, {
responseType: 'blob',
});
const blob = new Blob([response.data]);
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
link.click();
window.URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error('Download failed:', error.message);
}
};const CancelToken = axios.CancelToken;
const source = CancelToken.source();
api.get('/students', {
cancelToken: source.token
}).catch(error => {
if (axios.isCancel(error)) {
console.log('Request canceled:', error.message);
}
});
// Cancel the request
source.cancel('Operation canceled by the user.');{
data: {
success: true,
message: 'Students fetched successfully',
data: [...], // Array of students
pagination: {
page: 1,
limit: 10,
total: 100,
totalPages: 10
}
},
status: 200,
statusText: 'OK',
headers: {...},
config: {...}
}{
message: 'Validation failed',
type: 'VALIDATION_ERROR',
errors: {
email: 'Email already exists',
phone: 'Invalid phone number'
}
}In development mode, you'll see:
🚀 [API Request] POST http://localhost:5000/api/v1/students
✅ [API Response] POST /students - 342ms
❌ [400] Bad Request: Email already exists
❌ [401] Unauthorized: Session expired
❌ [500] Server Error: Internal server error
- Token is checked on every request
- Expired tokens trigger automatic logout
withCredentials: trueenables cookie sending- Backend can set CSRF tokens
- Content-Type headers prevent MIME sniffing
- JSON-only responses expected
- 429 errors handled gracefully
- User-friendly error messages
// ✅ Good - Create service file
import api from '@/services/api';
export const studentService = {
getAll: () => api.get('/students'),
getById: (id) => api.get(`/students/${id}`),
create: (data) => api.post('/students', data),
update: (id, data) => api.put(`/students/${id}`, data),
delete: (id) => api.delete(`/students/${id}`),
};// ✅ Good - Proper error handling
const fetchStudents = async () => {
try {
setLoading(true);
const response = await studentService.getAll();
setStudents(response.data.data);
} catch (error) {
setError(error.message);
showToast(error.message, 'error');
} finally {
setLoading(false);
}
};// ✅ Good - Clean async/await
const handleSubmit = async (data) => {
try {
await studentService.create(data);
showToast('Student created successfully', 'success');
refreshList();
} catch (error) {
showToast(error.message, 'error');
}
};frontend/src/services/
├── api.js ✅ Axios instance
├── student.service.js ✅ Student API calls
├── auth.service.js ✅ Authentication calls
├── trainer.service.js ✅ Trainer API calls
└── attendance.service.js ✅ Attendance API calls
| Method | Description | Example |
|---|---|---|
api.get() |
GET request | api.get('/students') |
api.post() |
POST request | api.post('/students', data) |
api.put() |
PUT request | api.put('/students/:id', data) |
api.patch() |
PATCH request | api.patch('/students/:id/status', data) |
api.delete() |
DELETE request | api.delete('/students/:id') |
- Axios instance created
- Base URL configured
- Timeout set (15s)
- Request interceptor (token injection)
- Response interceptor (error handling)
- Authentication handling (401 redirect)
- Network error handling
- Development logging
- Utility functions exported
- All HTTP status codes handled
- TypeScript ready (JSDoc comments)
The API client is now production-ready with comprehensive error handling, automatic token management, and development logging!