API Integration Guide

Learn how to integrate Gentelella Admin Template with backend APIs and external services

Table of contents

  1. REST API Integration
    1. HTTP Client Setup
      1. Axios Configuration
    2. API Service Layer
      1. Base Service Class
      2. Specific Service Classes
  2. Real-time Integration
    1. WebSocket Connection
    2. Real-time Dashboard Updates
  3. Data Management
    1. State Management
    2. Data Caching
  4. Authentication Integration
    1. JWT Token Management
  5. Error Handling
    1. Global Error Handler
  6. Performance Optimization
    1. Request Batching
  7. Next Steps

REST API Integration

HTTP Client Setup

Axios Configuration

// src/js/api/http-client.js
import axios from 'axios';

class HttpClient {
  constructor() {
    this.client = axios.create({
      baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8080/api',
      timeout: 10000,
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    });
    
    this.setupInterceptors();
  }
  
  setupInterceptors() {
    // Request interceptor - add auth token
    this.client.interceptors.request.use(
      (config) => {
        const token = localStorage.getItem('auth_token');
        if (token) {
          config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
      },
      (error) => Promise.reject(error)
    );
    
    // Response interceptor - handle errors
    this.client.interceptors.response.use(
      (response) => response.data,
      (error) => {
        if (error.response?.status === 401) {
          this.handleUnauthorized();
        }
        return Promise.reject(this.formatError(error));
      }
    );
  }
  
  handleUnauthorized() {
    localStorage.removeItem('auth_token');
    localStorage.removeItem('user_data');
    window.location.href = '/login.html';
  }
  
  formatError(error) {
    if (error.response) {
      return {
        message: error.response.data?.message || 'Server error',
        status: error.response.status,
        data: error.response.data
      };
    } else if (error.request) {
      return {
        message: 'Network error - please check your connection',
        status: 0
      };
    } else {
      return {
        message: error.message || 'Unknown error occurred',
        status: -1
      };
    }
  }
  
  // HTTP methods
  get(url, config = {}) {
    return this.client.get(url, config);
  }
  
  post(url, data = {}, config = {}) {
    return this.client.post(url, data, config);
  }
  
  put(url, data = {}, config = {}) {
    return this.client.put(url, data, config);
  }
  
  patch(url, data = {}, config = {}) {
    return this.client.patch(url, data, config);
  }
  
  delete(url, config = {}) {
    return this.client.delete(url, config);
  }
  
  // File upload
  upload(url, file, onProgress = null) {
    const formData = new FormData();
    formData.append('file', file);
    
    return this.client.post(url, formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      },
      onUploadProgress: (progressEvent) => {
        if (onProgress) {
          const progress = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
          );
          onProgress(progress);
        }
      }
    });
  }
}

// Create singleton instance
export const httpClient = new HttpClient();

API Service Layer

Base Service Class

// src/js/api/base-service.js
import { httpClient } from './http-client.js';

export class BaseService {
  constructor(endpoint) {
    this.endpoint = endpoint;
    this.http = httpClient;
  }
  
  async getAll(params = {}) {
    try {
      const response = await this.http.get(this.endpoint, { params });
      return {
        success: true,
        data: response.data,
        meta: response.meta
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        details: error
      };
    }
  }
  
  async getById(id) {
    try {
      const response = await this.http.get(`${this.endpoint}/${id}`);
      return {
        success: true,
        data: response.data
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        details: error
      };
    }
  }
  
  async create(data) {
    try {
      const response = await this.http.post(this.endpoint, data);
      return {
        success: true,
        data: response.data
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        details: error
      };
    }
  }
  
  async update(id, data) {
    try {
      const response = await this.http.put(`${this.endpoint}/${id}`, data);
      return {
        success: true,
        data: response.data
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        details: error
      };
    }
  }
  
  async delete(id) {
    try {
      await this.http.delete(`${this.endpoint}/${id}`);
      return {
        success: true
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        details: error
      };
    }
  }
  
  async search(query, params = {}) {
    try {
      const response = await this.http.get(`${this.endpoint}/search`, {
        params: { q: query, ...params }
      });
      return {
        success: true,
        data: response.data,
        meta: response.meta
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        details: error
      };
    }
  }
}

Specific Service Classes

// src/js/api/user-service.js
import { BaseService } from './base-service.js';

class UserService extends BaseService {
  constructor() {
    super('/users');
  }
  
  async authenticate(credentials) {
    try {
      const response = await this.http.post('/auth/login', credentials);
      
      // Store auth token
      if (response.token) {
        localStorage.setItem('auth_token', response.token);
        localStorage.setItem('user_data', JSON.stringify(response.user));
      }
      
      return {
        success: true,
        data: response
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  async logout() {
    try {
      await this.http.post('/auth/logout');
    } catch (error) {
      console.warn('Logout API call failed:', error.message);
    } finally {
      localStorage.removeItem('auth_token');
      localStorage.removeItem('user_data');
      window.location.href = '/login.html';
    }
  }
  
  async getCurrentUser() {
    try {
      const response = await this.http.get('/auth/me');
      return {
        success: true,
        data: response.data
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  async updateProfile(data) {
    try {
      const response = await this.http.put('/auth/profile', data);
      
      // Update stored user data
      localStorage.setItem('user_data', JSON.stringify(response.data));
      
      return {
        success: true,
        data: response.data
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  async changePassword(passwordData) {
    try {
      const response = await this.http.post('/auth/change-password', passwordData);
      return {
        success: true,
        data: response
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  async uploadAvatar(file, onProgress) {
    try {
      const response = await this.http.upload('/auth/avatar', file, onProgress);
      return {
        success: true,
        data: response.data
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
}

export const userService = new UserService();

// src/js/api/dashboard-service.js
import { BaseService } from './base-service.js';

class DashboardService extends BaseService {
  constructor() {
    super('/dashboard');
  }
  
  async getStats(dateRange = '30d') {
    try {
      const response = await this.http.get('/dashboard/stats', {
        params: { range: dateRange }
      });
      return {
        success: true,
        data: response.data
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  async getChartData(chartType, params = {}) {
    try {
      const response = await this.http.get(`/dashboard/charts/${chartType}`, {
        params
      });
      return {
        success: true,
        data: response.data
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  async getRecentActivity(limit = 10) {
    try {
      const response = await this.http.get('/dashboard/activity', {
        params: { limit }
      });
      return {
        success: true,
        data: response.data
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
}

export const dashboardService = new DashboardService();

Real-time Integration

WebSocket Connection

// src/js/api/websocket-client.js
class WebSocketClient {
  constructor() {
    this.ws = null;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
    this.reconnectDelay = 1000;
    this.listeners = new Map();
    this.isConnected = false;
  }
  
  connect() {
    const wsUrl = import.meta.env.VITE_WS_URL || 'ws://localhost:8080/ws';
    const token = localStorage.getItem('auth_token');
    
    this.ws = new WebSocket(`${wsUrl}?token=${token}`);
    
    this.ws.onopen = () => {
      console.log('WebSocket connected');
      this.isConnected = true;
      this.reconnectAttempts = 0;
      this.emit('connected');
    };
    
    this.ws.onmessage = (event) => {
      try {
        const message = JSON.parse(event.data);
        this.handleMessage(message);
      } catch (error) {
        console.error('Failed to parse WebSocket message:', error);
      }
    };
    
    this.ws.onclose = () => {
      console.log('WebSocket disconnected');
      this.isConnected = false;
      this.emit('disconnected');
      this.reconnect();
    };
    
    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
      this.emit('error', error);
    };
  }
  
  reconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error('Max reconnection attempts reached');
      return;
    }
    
    this.reconnectAttempts++;
    const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
    
    console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
    
    setTimeout(() => {
      this.connect();
    }, delay);
  }
  
  handleMessage(message) {
    const { type, data } = message;
    this.emit(type, data);
  }
  
  send(type, data = {}) {
    if (!this.isConnected) {
      console.warn('WebSocket not connected');
      return false;
    }
    
    const message = JSON.stringify({ type, data });
    this.ws.send(message);
    return true;
  }
  
  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(callback);
  }
  
  off(event, callback) {
    if (!this.listeners.has(event)) return;
    
    const callbacks = this.listeners.get(event);
    const index = callbacks.indexOf(callback);
    
    if (index > -1) {
      callbacks.splice(index, 1);
    }
  }
  
  emit(event, data) {
    if (!this.listeners.has(event)) return;
    
    this.listeners.get(event).forEach(callback => {
      try {
        callback(data);
      } catch (error) {
        console.error(`Error in WebSocket event handler for ${event}:`, error);
      }
    });
  }
  
  disconnect() {
    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }
    this.isConnected = false;
  }
}

// Create singleton instance
export const wsClient = new WebSocketClient();

// Auto-connect if user is authenticated
if (localStorage.getItem('auth_token')) {
  wsClient.connect();
}

Real-time Dashboard Updates

// src/js/dashboard/real-time-dashboard.js
import { wsClient } from '../api/websocket-client.js';
import { dashboardService } from '../api/dashboard-service.js';

class RealTimeDashboard {
  constructor() {
    this.charts = new Map();
    this.stats = new Map();
    this.init();
  }
  
  init() {
    this.setupWebSocketListeners();
    this.loadInitialData();
  }
  
  setupWebSocketListeners() {
    // Listen for real-time stats updates
    wsClient.on('stats.update', (data) => {
      this.updateStats(data);
    });
    
    // Listen for new chart data
    wsClient.on('chart.data', (data) => {
      this.updateChart(data.chartId, data.data);
    });
    
    // Listen for new notifications
    wsClient.on('notification', (data) => {
      this.showNotification(data);
    });
    
    // Listen for user activity
    wsClient.on('user.activity', (data) => {
      this.updateActivityFeed(data);
    });
  }
  
  async loadInitialData() {
    try {
      // Load dashboard stats
      const statsResult = await dashboardService.getStats();
      if (statsResult.success) {
        this.renderStats(statsResult.data);
      }
      
      // Load chart data
      const chartTypes = ['sales', 'users', 'revenue'];
      for (const chartType of chartTypes) {
        const chartResult = await dashboardService.getChartData(chartType);
        if (chartResult.success) {
          this.renderChart(chartType, chartResult.data);
        }
      }
      
      // Load recent activity
      const activityResult = await dashboardService.getRecentActivity();
      if (activityResult.success) {
        this.renderActivity(activityResult.data);
      }
    } catch (error) {
      console.error('Failed to load dashboard data:', error);
    }
  }
  
  updateStats(data) {
    Object.entries(data).forEach(([key, value]) => {
      const element = document.querySelector(`[data-stat="${key}"]`);
      if (element) {
        // Animate value change
        this.animateValue(element, value);
      }
    });
  }
  
  animateValue(element, newValue) {
    const currentValue = parseFloat(element.textContent.replace(/[^0-9.-]/g, '')) || 0;
    const difference = newValue - currentValue;
    const steps = 30;
    const stepValue = difference / steps;
    let current = currentValue;
    
    const timer = setInterval(() => {
      current += stepValue;
      element.textContent = this.formatValue(current, element.dataset.format);
      
      if (--steps <= 0) {
        clearInterval(timer);
        element.textContent = this.formatValue(newValue, element.dataset.format);
      }
    }, 16);
  }
  
  formatValue(value, format) {
    switch (format) {
      case 'currency':
        return new Intl.NumberFormat('en-US', {
          style: 'currency',
          currency: 'USD'
        }).format(value);
      case 'percentage':
        return `${value.toFixed(1)}%`;
      case 'number':
        return new Intl.NumberFormat('en-US').format(Math.round(value));
      default:
        return value.toString();
    }
  }
  
  updateChart(chartId, newData) {
    const chart = this.charts.get(chartId);
    if (!chart) return;
    
    // Update chart data
    chart.data = newData;
    chart.update('active');
  }
  
  showNotification(data) {
    // Use notification plugin or create custom notification
    if (window.GentelellaPlugins && window.GentelellaPlugins.getPlugin('notifications')) {
      const notifications = window.GentelellaPlugins.getPlugin('notifications');
      notifications.show(data.message, data.type);
    }
  }
  
  updateActivityFeed(activity) {
    const feedContainer = document.querySelector('#activity-feed');
    if (!feedContainer) return;
    
    const activityItem = document.createElement('div');
    activityItem.className = 'activity-item';
    activityItem.innerHTML = `
      <div class="activity-icon">
        <i class="fa fa-${activity.icon}"></i>
      </div>
      <div class="activity-content">
        <div class="activity-text">${activity.message}</div>
        <div class="activity-time">${this.formatTime(activity.timestamp)}</div>
      </div>
    `;
    
    // Add to top of feed
    feedContainer.insertBefore(activityItem, feedContainer.firstChild);
    
    // Remove oldest items if feed is too long
    const items = feedContainer.querySelectorAll('.activity-item');
    if (items.length > 10) {
      for (let i = 10; i < items.length; i++) {
        items[i].remove();
      }
    }
  }
  
  formatTime(timestamp) {
    const date = new Date(timestamp);
    const now = new Date();
    const diff = now - date;
    
    if (diff < 60000) return 'Just now';
    if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
    if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
    return date.toLocaleDateString();
  }
}

// Initialize real-time dashboard
new RealTimeDashboard();

Data Management

State Management

// src/js/store/app-store.js
class AppStore {
  constructor() {
    this.state = {
      user: null,
      theme: 'light',
      sidebarCollapsed: false,
      notifications: [],
      loading: false,
      error: null
    };
    
    this.listeners = new Map();
    this.loadFromStorage();
  }
  
  // Get current state
  getState() {
    return { ...this.state };
  }
  
  // Update state
  setState(updates) {
    const prevState = { ...this.state };
    this.state = { ...this.state, ...updates };
    
    // Notify listeners
    this.notifyListeners(prevState, this.state);
    
    // Persist certain state to localStorage
    this.saveToStorage();
  }
  
  // Subscribe to state changes
  subscribe(listener) {
    const id = Date.now() + Math.random();
    this.listeners.set(id, listener);
    
    // Return unsubscribe function
    return () => {
      this.listeners.delete(id);
    };
  }
  
  notifyListeners(prevState, newState) {
    this.listeners.forEach(listener => {
      try {
        listener(newState, prevState);
      } catch (error) {
        console.error('Error in state listener:', error);
      }
    });
  }
  
  loadFromStorage() {
    try {
      const userData = localStorage.getItem('user_data');
      if (userData) {
        this.state.user = JSON.parse(userData);
      }
      
      const theme = localStorage.getItem('theme');
      if (theme) {
        this.state.theme = theme;
      }
      
      const sidebarCollapsed = localStorage.getItem('sidebar-collapsed');
      if (sidebarCollapsed) {
        this.state.sidebarCollapsed = sidebarCollapsed === 'true';
      }
    } catch (error) {
      console.error('Failed to load state from storage:', error);
    }
  }
  
  saveToStorage() {
    try {
      if (this.state.user) {
        localStorage.setItem('user_data', JSON.stringify(this.state.user));
      }
      
      localStorage.setItem('theme', this.state.theme);
      localStorage.setItem('sidebar-collapsed', this.state.sidebarCollapsed.toString());
    } catch (error) {
      console.error('Failed to save state to storage:', error);
    }
  }
  
  // Action methods
  setUser(user) {
    this.setState({ user });
  }
  
  clearUser() {
    this.setState({ user: null });
    localStorage.removeItem('user_data');
    localStorage.removeItem('auth_token');
  }
  
  setTheme(theme) {
    this.setState({ theme });
    document.documentElement.setAttribute('data-theme', theme);
  }
  
  toggleSidebar() {
    this.setState({ sidebarCollapsed: !this.state.sidebarCollapsed });
  }
  
  addNotification(notification) {
    const notifications = [...this.state.notifications, {
      id: Date.now(),
      timestamp: new Date(),
      ...notification
    }];
    this.setState({ notifications });
  }
  
  removeNotification(id) {
    const notifications = this.state.notifications.filter(n => n.id !== id);
    this.setState({ notifications });
  }
  
  setLoading(loading) {
    this.setState({ loading });
  }
  
  setError(error) {
    this.setState({ error });
  }
  
  clearError() {
    this.setState({ error: null });
  }
}

// Create singleton instance
export const appStore = new AppStore();

// Helper hook for components
export function useStore(selector) {
  const state = appStore.getState();
  return selector ? selector(state) : state;
}

Data Caching

// src/js/cache/data-cache.js
class DataCache {
  constructor() {
    this.cache = new Map();
    this.expiry = new Map();
    this.defaultTTL = 5 * 60 * 1000; // 5 minutes
  }
  
  set(key, data, ttl = this.defaultTTL) {
    this.cache.set(key, data);
    this.expiry.set(key, Date.now() + ttl);
  }
  
  get(key) {
    if (!this.cache.has(key)) {
      return null;
    }
    
    const expiryTime = this.expiry.get(key);
    if (Date.now() > expiryTime) {
      this.delete(key);
      return null;
    }
    
    return this.cache.get(key);
  }
  
  has(key) {
    return this.get(key) !== null;
  }
  
  delete(key) {
    this.cache.delete(key);
    this.expiry.delete(key);
  }
  
  clear() {
    this.cache.clear();
    this.expiry.clear();
  }
  
  cleanup() {
    const now = Date.now();
    for (const [key, expiryTime] of this.expiry.entries()) {
      if (now > expiryTime) {
        this.delete(key);
      }
    }
  }
  
  size() {
    return this.cache.size;
  }
}

// Create singleton instance
export const dataCache = new DataCache();

// Auto cleanup every 5 minutes
setInterval(() => {
  dataCache.cleanup();
}, 5 * 60 * 1000);

Authentication Integration

JWT Token Management

// src/js/auth/auth-manager.js
class AuthManager {
  constructor() {
    this.token = localStorage.getItem('auth_token');
    this.refreshTimer = null;
    this.init();
  }
  
  init() {
    if (this.token) {
      this.scheduleTokenRefresh();
    }
  }
  
  async login(credentials) {
    try {
      const response = await userService.authenticate(credentials);
      
      if (response.success) {
        this.token = response.data.token;
        this.scheduleTokenRefresh();
        
        // Update app state
        appStore.setUser(response.data.user);
        
        return response;
      }
      
      return response;
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  logout() {
    this.clearTokenRefresh();
    this.token = null;
    
    // Clear app state
    appStore.clearUser();
    
    // Call logout service
    userService.logout();
  }
  
  isAuthenticated() {
    return !!this.token && !this.isTokenExpired();
  }
  
  isTokenExpired() {
    if (!this.token) return true;
    
    try {
      const payload = JSON.parse(atob(this.token.split('.')[1]));
      return payload.exp * 1000 < Date.now();
    } catch (error) {
      return true;
    }
  }
  
  async refreshToken() {
    try {
      const response = await httpClient.post('/auth/refresh');
      
      if (response.token) {
        this.token = response.token;
        localStorage.setItem('auth_token', this.token);
        this.scheduleTokenRefresh();
        return true;
      }
      
      return false;
    } catch (error) {
      console.error('Token refresh failed:', error);
      this.logout();
      return false;
    }
  }
  
  scheduleTokenRefresh() {
    this.clearTokenRefresh();
    
    if (!this.token) return;
    
    try {
      const payload = JSON.parse(atob(this.token.split('.')[1]));
      const expiryTime = payload.exp * 1000;
      const refreshTime = expiryTime - (5 * 60 * 1000); // 5 minutes before expiry
      const timeUntilRefresh = refreshTime - Date.now();
      
      if (timeUntilRefresh > 0) {
        this.refreshTimer = setTimeout(() => {
          this.refreshToken();
        }, timeUntilRefresh);
      } else {
        // Token expired or will expire soon
        this.refreshToken();
      }
    } catch (error) {
      console.error('Failed to schedule token refresh:', error);
    }
  }
  
  clearTokenRefresh() {
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer);
      this.refreshTimer = null;
    }
  }
  
  getToken() {
    return this.token;
  }
  
  getUser() {
    const userData = localStorage.getItem('user_data');
    return userData ? JSON.parse(userData) : null;
  }
}

// Create singleton instance
export const authManager = new AuthManager();

// Route protection
export function requireAuth() {
  if (!authManager.isAuthenticated()) {
    window.location.href = '/login.html';
    return false;
  }
  return true;
}

// Auto-redirect if not authenticated (for protected pages)
if (document.querySelector('[data-require-auth]')) {
  requireAuth();
}

Error Handling

Global Error Handler

// src/js/error/error-handler.js
class ErrorHandler {
  constructor() {
    this.setupGlobalHandlers();
  }
  
  setupGlobalHandlers() {
    // Handle unhandled promise rejections
    window.addEventListener('unhandledrejection', (event) => {
      console.error('Unhandled promise rejection:', event.reason);
      this.handleError(event.reason, 'Promise Rejection');
      event.preventDefault();
    });
    
    // Handle JavaScript errors
    window.addEventListener('error', (event) => {
      console.error('JavaScript error:', event.error);
      this.handleError(event.error, 'JavaScript Error');
    });
    
    // Handle API errors
    document.addEventListener('api-error', (event) => {
      this.handleApiError(event.detail);
    });
  }
  
  handleError(error, context = 'Unknown') {
    const errorInfo = {
      message: error.message || 'Unknown error',
      stack: error.stack,
      context,
      timestamp: new Date(),
      userAgent: navigator.userAgent,
      url: window.location.href,
      user: authManager.getUser()?.id
    };
    
    // Log to console
    console.error('Error handled:', errorInfo);
    
    // Send to error tracking service
    this.reportError(errorInfo);
    
    // Show user-friendly notification
    this.showErrorNotification(error);
  }
  
  handleApiError(error) {
    if (error.status === 401) {
      this.handleUnauthorized();
    } else if (error.status >= 500) {
      this.showErrorNotification({
        message: 'Server error occurred. Please try again later.'
      });
    } else {
      this.showErrorNotification(error);
    }
  }
  
  handleUnauthorized() {
    // Clear auth data and redirect to login
    authManager.logout();
  }
  
  showErrorNotification(error) {
    // Use notification plugin if available
    if (window.GentelellaPlugins && window.GentelellaPlugins.getPlugin('notifications')) {
      const notifications = window.GentelellaPlugins.getPlugin('notifications');
      notifications.show(error.message || 'An error occurred', 'error');
    } else {
      // Fallback to alert
      alert(error.message || 'An error occurred');
    }
  }
  
  async reportError(errorInfo) {
    try {
      // Send error to monitoring service
      await httpClient.post('/errors/report', errorInfo);
    } catch (reportingError) {
      console.error('Failed to report error:', reportingError);
    }
  }
}

// Initialize global error handler
new ErrorHandler();

Performance Optimization

Request Batching

// src/js/api/request-batcher.js
class RequestBatcher {
  constructor() {
    this.batches = new Map();
    this.batchDelay = 100; // ms
  }
  
  batch(endpoint, id, params = {}) {
    return new Promise((resolve, reject) => {
      if (!this.batches.has(endpoint)) {
        this.batches.set(endpoint, {
          requests: [],
          timer: null
        });
      }
      
      const batch = this.batches.get(endpoint);
      batch.requests.push({ id, params, resolve, reject });
      
      // Clear existing timer and set new one
      if (batch.timer) {
        clearTimeout(batch.timer);
      }
      
      batch.timer = setTimeout(() => {
        this.executeBatch(endpoint);
      }, this.batchDelay);
    });
  }
  
  async executeBatch(endpoint) {
    const batch = this.batches.get(endpoint);
    if (!batch || batch.requests.length === 0) return;
    
    const requests = batch.requests.slice();
    batch.requests = [];
    batch.timer = null;
    
    try {
      const ids = requests.map(req => req.id);
      const response = await httpClient.post(`${endpoint}/batch`, { ids });
      
      // Resolve individual requests
      requests.forEach(request => {
        const result = response.data.find(item => item.id === request.id);
        if (result) {
          request.resolve(result);
        } else {
          request.reject(new Error('Item not found in batch response'));
        }
      });
    } catch (error) {
      // Reject all requests
      requests.forEach(request => {
        request.reject(error);
      });
    }
  }
}

export const requestBatcher = new RequestBatcher();

Next Steps


💡 Pro Tip: Always implement proper error handling and retry logic for API calls. Use caching strategically to reduce API load and improve user experience.