Now.js Framework Documentation

Now.js Framework Documentation

ApiService - High-level HTTP Service

EN 31 Oct 2025 01:12

ApiService - High-level HTTP Service

Documentation for ApiService, a High-level HTTP Service in the Now.js Framework

📋 Table of Contents

  1. Overview
  2. Installation and Import
  3. Getting Started
  4. Configuration
  5. HTTP Methods
  6. Caching System
  7. Authentication
  8. Request Deduplication
  9. Retry Mechanism
  10. Cache Management
  11. Advanced Features
  12. Usage Examples
  13. API Reference
  14. Best Practices

Overview

ApiService is a High-level HTTP Service built to handle complex HTTP requests with advanced features that enhance performance and security.

Key Features

  • In-Memory Caching: Cache data in memory with expiration
  • Request Deduplication: Prevent duplicate concurrent API calls
  • Retry Mechanism: Automatic retry on network errors
  • Authentication Management: Support Bearer, Basic, OAuth, Hybrid
  • CSRF Protection: Automatic CSRF attack prevention
  • JWT Auto-Refresh: Automatic token refresh before expiration
  • Request Tracking: Track performance and errors
  • Abort Controllers: Cancel pending requests
  • Polling Support: Automatic endpoint polling
  • Sequence Requests: Execute requests sequentially

When to Use ApiService

Use ApiService when:

  • You need caching for data that doesn't change frequently
  • You need retry logic for network errors
  • You need authentication management
  • You need to prevent duplicate API calls
  • You need request tracking and analytics

Don't use ApiService when:

  • You need simplicity (use simpleFetch or http instead)
  • Working with real-time data (shouldn't be cached)
  • You don't need advanced features

Installation and Import

ApiService is loaded with the Now.js Framework and is ready to use immediately via the window object:

// No import needed - ready to use immediately
console.log(window.ApiService); // ApiService object

Getting Started

Basic Initialization

// Initialize with default config
await ApiService.init();

// Now you can make requests
const response = await ApiService.get('/api/users');
console.log(response.data);

Initialization with Config

await ApiService.init({
  baseURL: 'https://api.example.com',
  debug: true,

  cache: {
    enabled: true,
    expiry: {
      get: 300000, // Cache GET requests for 5 minutes
      post: 0,     // Don't cache POST
      put: 0,
      delete: 0
    }
  },

  security: {
    csrfProtection: true,
    bearerAuth: true,
    bearerTokenKey: 'auth_token'
  },

  connection: {
    timeout: 30000,
    retryOnNetworkError: true,
    maxNetworkRetries: 3
  }
});

Configuration

ApiService has multiple configuration sections that can be customized.

1. General Settings

{
  baseURL: '',              // Base URL for all requests
  debug: false,             // Enable debug logging
  retryCount: 3,            // Number of retries
  retryDelay: 1000,         // Delay between retries (ms)
  deduplicate: true         // Enable request deduplication
}

2. Security Settings

{
  security: {
    // CSRF Protection
    csrfProtection: true,
    csrfHeaderName: 'X-CSRF-Token',
    csrfCookieName: 'XSRF-TOKEN',
    csrfTokenSelector: 'meta[name="csrf-token"]',
    csrfIncludeSafeMethods: true,

    // Bearer Authentication
    bearerAuth: false,
    bearerTokenKey: 'auth_token',
    bearerPrefix: 'Bearer ',

    // Basic Authentication
    basicAuth: false,
    basicUsername: '',
    basicPassword: '',

    // OAuth
    oauth: false,
    oauthTokenKey: 'oauth_token',

    // JWT Auto-Refresh
    jwtRefresh: false,
    jwtRefreshEndpoint: 'api/auth/refresh',
    jwtExpireKey: 'exp',
    jwtRefreshBeforeExpirySec: 300,

    // Authentication Strategy
    authStrategy: 'hybrid',  // 'hybrid', 'cookie', 'storage'
    sendCredentials: true    // Send cookies with requests
  }
}

3. Connection Settings

{
  connection: {
    timeout: 30000,                           // Request timeout (ms)
    retryOnNetworkError: true,                // Retry on network errors
    maxNetworkRetries: 3,                     // Max retry attempts
    backoffFactor: 1.5,                       // Backoff multiplier
    retryStatusCodes: [408, 429, 500, 502, 503, 504],
    exponentialBackoff: true                  // Use exponential backoff
  }
}

4. Cache Settings

{
  cache: {
    enabled: true,
    storageType: 'memory',     // 'memory', 'session', 'local'
    maxSize: 100,              // Max cache entries
    expiry: {
      default: 60000,          // 1 minute
      get: 60000,              // Cache GET for 1 minute
      post: 0,                 // Don't cache POST
      put: 0,                  // Don't cache PUT
      delete: 0                // Don't cache DELETE
    },
    keyGeneratorFn: null,      // Custom cache key function
    responsePredicate: null    // Function to decide caching
  }
}

5. Tracking Settings

{
  tracking: {
    enabled: false,
    errorTracking: true,
    performanceTracking: false,
    analyticsCallback: null,   // Function to receive analytics
    excludePaths: []           // Paths to exclude from tracking
  }
}

6. Logging Settings

{
  logging: {
    enabled: false,
    logLevel: 'error',         // 'debug', 'info', 'warn', 'error'
    includeRequest: true,
    includeResponse: true,
    logToConsole: true,
    customLogger: null         // Custom logging function
  }
}

HTTP Methods

ApiService provides main HTTP methods for use.

GET Request

// Simple GET
const response = await ApiService.get('/api/users');
console.log(response.data);

// GET with parameters
const response = await ApiService.get('/api/users', {
  page: 1,
  limit: 10,
  sort: 'name'
});

// GET with options
const response = await ApiService.get('/api/users', {}, {
  cache: { enabled: false },  // Disable cache for this request
  timeout: 5000               // Custom timeout
});

POST Request

// Simple POST
const response = await ApiService.post('/api/users', {
  name: 'John Doe',
  email: 'john@example.com'
});

// POST with options
const response = await ApiService.post('/api/users', userData, {
  headers: {
    'X-Custom-Header': 'value'
  }
});

PUT Request

// Update resource
const response = await ApiService.put('/api/users/1', {
  name: 'Jane Doe',
  email: 'jane@example.com'
});

DELETE Request

// Delete resource
const response = await ApiService.delete('/api/users/1');

// DELETE with options
const response = await ApiService.delete('/api/users/1', {
  headers: {
    'X-Reason': 'Account closed'
  }
});

Response Object

All methods return a response object with the same structure as HttpClient:

{
  ok: boolean,              // true if status 200-299
  status: number,           // HTTP status code
  statusText: string,       // HTTP status text
  data: any,                // Parsed response data
  headers: object,          // Response headers
  url: string,              // Request URL
  fromCache: boolean,       // true if from cache (GET only)
  timestamp: number,        // Timestamp (if available)
  requestId: string|null    // Request ID (if available)
}

Caching System

ApiService has a powerful and flexible caching system.

Automatic Caching

await ApiService.init({
  cache: {
    enabled: true,
    expiry: {
      get: 300000  // Cache GET requests for 5 minutes
    }
  }
});

// First call - will call API
const response1 = await ApiService.get('/api/categories');
console.log(response1.fromCache); // false

// Second call within 5 minutes - will get from cache
const response2 = await ApiService.get('/api/categories');
console.log(response2.fromCache); // true

Cache by Storage Type

Memory Cache (Default)

await ApiService.init({
  cache: {
    enabled: true,
    storageType: 'memory'  // Default - store in memory
  }
});

Advantages:

  • ✅ Fastest
  • ✅ No size limit (depends on memory)

Disadvantages:

  • ❌ Lost on page refresh

Session Storage Cache

await ApiService.init({
  cache: {
    enabled: true,
    storageType: 'session'  // Store in sessionStorage
  }
});

Advantages:

  • ✅ Persists throughout session
  • ✅ Fast

Disadvantages:

  • ❌ Lost when tab is closed
  • ❌ Size limited (~5-10MB)

Local Storage Cache

await ApiService.init({
  cache: {
    enabled: true,
    storageType: 'local'    // Store in localStorage
  }
});

Advantages:

  • ✅ Persists even after browser closes
  • ✅ Shared between tabs

Disadvantages:

  • ❌ Size limited (~5-10MB)
  • ❌ Slower than memory

Custom Cache Key

await ApiService.init({
  cache: {
    enabled: true,
    keyGeneratorFn: (url, params, method) => {
      // Custom cache key logic
      const userId = localStorage.getItem('user_id');
      return `${userId}:${method}:${url}:${JSON.stringify(params)}`;
    }
  }
});

Conditional Caching

await ApiService.init({
  cache: {
    enabled: true,
    responsePredicate: (response) => {
      // Cache only successful responses
      return response.ok && response.status === 200;
    }
  }
});

Cache Expiry by Method

await ApiService.init({
  cache: {
    enabled: true,
    expiry: {
      get: 600000,    // Cache GET for 10 minutes
      post: 0,        // Don't cache POST
      put: 0,         // Don't cache PUT
      delete: 0       // Don't cache DELETE
    }
  }
});

Authentication

ApiService supports multiple authentication methods.

1. Bearer Token Authentication

Storage Strategy (Legacy)

await ApiService.init({
  security: {
    bearerAuth: true,
    bearerTokenKey: 'auth_token',
    authStrategy: 'storage'  // Use localStorage
  }
});

// Set token in localStorage
localStorage.setItem('auth_token', 'your-token-here');

// Requests will automatically include: Authorization: Bearer your-token
const response = await ApiService.get('/api/user/profile');
await ApiService.init({
  security: {
    bearerAuth: true,
    authStrategy: 'hybrid'  // HttpOnly cookie + in-memory token
  }
});

// Set access token (from login response)
ApiService.setAccessToken('short-lived-access-token');

// Requests will include: Authorization: Bearer short-lived-access-token
const response = await ApiService.get('/api/user/profile');

// Clear token on logout
ApiService.clearAccessToken();
await ApiService.init({
  security: {
    authStrategy: 'cookie',  // Server handles auth via cookie
    sendCredentials: true
  }
});

// No Authorization header - server uses HttpOnly cookie
const response = await ApiService.get('/api/user/profile');

2. Basic Authentication

await ApiService.init({
  security: {
    basicAuth: true,
    basicUsername: 'admin',
    basicPassword: 'secret123'
  }
});

// Requests will include: Authorization: Basic YWRtaW46c2VjcmV0MTIz
const response = await ApiService.get('/api/admin/stats');

3. JWT Auto-Refresh

await ApiService.init({
  security: {
    bearerAuth: true,
    authStrategy: 'hybrid',
    jwtRefresh: true,
    jwtRefreshEndpoint: '/api/auth/refresh',
    jwtExpireKey: 'exp',
    jwtRefreshBeforeExpirySec: 300  // Refresh 5 minutes before expiry
  }
});

// ApiService will automatically:
// 1. Parse JWT token to check expiry
// 2. Refresh token 5 minutes before it expires
// 3. Update access token in memory

// You don't need to do anything - just use ApiService normally
const response = await ApiService.get('/api/user/profile');

4. CSRF Protection

await ApiService.init({
  security: {
    csrfProtection: true,
    csrfHeaderName: 'X-CSRF-Token',
    csrfCookieName: 'XSRF-TOKEN',
    csrfTokenSelector: 'meta[name="csrf-token"]'
  }
});

// CSRF token will be automatically:
// 1. Retrieved from meta tag or cookie
// 2. Attached to POST/PUT/DELETE requests

HTML:

<meta name="csrf-token" content="your-csrf-token">

Request Deduplication

ApiService prevents duplicate concurrent API calls.

How It Works

await ApiService.init({
  deduplicate: true  // Default
});

// User clicks button multiple times rapidly
button.addEventListener('click', async () => {
  const response = await ApiService.get('/api/data');
  console.log(response.data);
});

// ApiService will:
// 1. Check if request is already pending
// 2. If yes, return the same promise
// 3. If no, make new request
// Result: Only 1 actual API call, all clicks share the same response

Disable Deduplication

await ApiService.init({
  deduplicate: false  // Allow duplicate requests
});

Manual Cache Key

Deduplication uses cache key to identify requests:

// Same cache key = deduplicated
await ApiService.get('/api/users', { page: 1 });
await ApiService.get('/api/users', { page: 1 });  // Shares first request

// Different cache key = separate requests
await ApiService.get('/api/users', { page: 1 });
await ApiService.get('/api/users', { page: 2 });  // Makes second request

Retry Mechanism

ApiService automatically retries on errors.

Basic Retry

await ApiService.init({
  retryCount: 3,
  retryDelay: 1000,
  connection: {
    retryOnNetworkError: true,
    maxNetworkRetries: 3
  }
});

// ApiService will retry automatically on:
// 1. Network errors
// 2. Timeout errors
// 3. Server errors (500, 502, 503, 504)

Exponential Backoff

await ApiService.init({
  retryDelay: 1000,
  connection: {
    retryOnNetworkError: true,
    maxNetworkRetries: 3,
    backoffFactor: 1.5,
    exponentialBackoff: true
  }
});

// Retry delays:
// 1st retry: 1000ms
// 2nd retry: 1500ms (1000 * 1.5)
// 3rd retry: 2250ms (1500 * 1.5)

Custom Retry Status Codes

await ApiService.init({
  connection: {
    retryStatusCodes: [408, 429, 500, 502, 503, 504],
    maxNetworkRetries: 3
  }
});

// Will retry on:
// 408 - Request Timeout
// 429 - Too Many Requests
// 500 - Internal Server Error
// 502 - Bad Gateway
// 503 - Service Unavailable
// 504 - Gateway Timeout

Cache Management

ApiService provides methods for cache management.

Clear All Cache

// Clear all cache entries
ApiService.clearCache();

Invalidate Specific Cache

// Invalidate by URL and params
ApiService.invalidateCache('/api/users', { page: 1 });

// Invalidate by URL (all params)
ApiService.invalidateCacheByUrl('/api/users');

Automatic Cache Invalidation

// POST/PUT/DELETE automatically invalidate related cache
await ApiService.post('/api/users', userData);
// ^ This will invalidate cache for '/api/users'

await ApiService.put('/api/users/1', userData);
// ^ This will invalidate cache for '/api/users/1'

await ApiService.delete('/api/users/1');
// ^ This will invalidate cache for '/api/users/1'

Check Cache Status

const response = await ApiService.get('/api/users');

if (response.fromCache) {
  console.log('Data from cache');
} else {
  console.log('Data from API');
}

Advanced Features

1. Abort Requests

// Start request
const requestPromise = ApiService.get('/api/large-data');

// Abort after 5 seconds
setTimeout(() => {
  ApiService.abort('/api/large-data');
}, 5000);

try {
  const response = await requestPromise;
} catch (error) {
  console.log('Request aborted');
}

2. Polling

// Poll every 5 seconds
const stopPolling = ApiService.poll(
  '/api/status',
  {},
  5000,
  (response, error, count) => {
    if (error) {
      console.error('Poll error:', error);
      return;
    }

    console.log('Poll #' + count, response.data);
  },
  {
    maxPolls: 10,  // Stop after 10 polls
    condition: (response) => {
      // Stop when status is 'completed'
      return response.data.status === 'completed';
    }
  }
);

// Stop polling manually
// stopPolling();

3. Sequential Requests

const requests = [
  { url: '/api/users', params: { page: 1 } },
  { url: '/api/posts', params: { page: 1 } },
  { url: '/api/comments', params: { page: 1 } }
];

const results = await ApiService.sequence(requests, (response) => {
  // Process each response
  return response.data;
});

console.log('All results:', results);

4. Parallel Requests

const responses = await ApiService.all([
  ApiService.get('/api/users'),
  ApiService.get('/api/posts'),
  ApiService.get('/api/comments')
]);

console.log('Users:', responses[0].data);
console.log('Posts:', responses[1].data);
console.log('Comments:', responses[2].data);

5. Custom Logging

await ApiService.init({
  logging: {
    enabled: true,
    logLevel: 'debug',
    customLogger: (level, message, data) => {
      // Send to external logging service
      console.log(`[${level.toUpperCase()}] ${message}`, data);

      // Example: Send to Sentry
      if (level === 'error') {
        // Sentry.captureException(data);
      }
    }
  }
});

6. Performance Tracking

await ApiService.init({
  tracking: {
    enabled: true,
    performanceTracking: true,
    analyticsCallback: (data) => {
      if (data.type === 'performance') {
        console.log(`${data.method} ${data.url} took ${data.duration}ms`);

        // Send to analytics service
        // analytics.track('api_request', data);
      }
    }
  }
});

Usage Examples

1. Simple API Call with Caching

await ApiService.init({
  baseURL: 'https://api.example.com',
  cache: {
    enabled: true,
    expiry: { get: 300000 }  // 5 minutes
  }
});

// First call
const categories = await ApiService.get('/api/categories');
console.log(categories.fromCache); // false

// Second call (within 5 minutes)
const categoriesCached = await ApiService.get('/api/categories');
console.log(categoriesCached.fromCache); // true

2. Authentication with Bearer Token

await ApiService.init({
  baseURL: 'https://api.example.com',
  security: {
    bearerAuth: true,
    authStrategy: 'hybrid'
  }
});

// Login
const loginResponse = await ApiService.post('/api/auth/login', {
  email: 'user@example.com',
  password: 'password123'
});

// Set access token
ApiService.setAccessToken(loginResponse.data.accessToken);

// Make authenticated requests
const profile = await ApiService.get('/api/user/profile');
console.log(profile.data);

// Logout
await ApiService.post('/api/auth/logout');
ApiService.clearAccessToken();

3. Form Submission with CSRF

await ApiService.init({
  security: {
    csrfProtection: true
  }
});

// CSRF token will be automatically attached
const response = await ApiService.post('/api/contact', {
  name: 'John Doe',
  email: 'john@example.com',
  message: 'Hello!'
});

4. Retry on Network Error

await ApiService.init({
  connection: {
    retryOnNetworkError: true,
    maxNetworkRetries: 3,
    backoffFactor: 1.5,
    exponentialBackoff: true
  }
});

// Will retry 3 times on network error
try {
  const response = await ApiService.get('/api/data');
  console.log(response.data);
} catch (error) {
  console.error('Failed after 3 retries:', error);
}

5. Polling for Status Updates

await ApiService.init({
  baseURL: 'https://api.example.com'
});

// Start a long-running task
const task = await ApiService.post('/api/tasks', {
  type: 'export',
  format: 'pdf'
});

// Poll for status
const stopPolling = ApiService.poll(
  `/api/tasks/${task.data.id}`,
  {},
  2000,  // Poll every 2 seconds
  (response, error, count) => {
    if (error) {
      console.error('Poll error:', error);
      return;
    }

    const status = response.data.status;
    console.log(`Poll #${count}: ${status}`);

    if (status === 'completed') {
      console.log('Task completed!', response.data.result);
    }
  },
  {
    maxPolls: 30,  // Max 1 minute (30 * 2 seconds)
    condition: (response) => {
      // Stop when completed or failed
      return ['completed', 'failed'].includes(response.data.status);
    }
  }
);

6. Pagination with Caching

await ApiService.init({
  baseURL: 'https://api.example.com',
  cache: {
    enabled: true,
    expiry: { get: 60000 }  // 1 minute
  }
});

async function loadPage(page) {
  const response = await ApiService.get('/api/products', {
    page: page,
    limit: 20
  });

  console.log(`Page ${page} (cached: ${response.fromCache})`);
  return response.data;
}

// Load pages 1-3
const page1 = await loadPage(1);  // API call
const page2 = await loadPage(2);  // API call
const page3 = await loadPage(3);  // API call

// Reload page 1 (within 1 minute)
const page1Again = await loadPage(1);  // From cache

7. File Upload

const fileInput = document.querySelector('#file');
const file = fileInput.files[0];

const formData = new FormData();
formData.append('file', file);
formData.append('title', 'My Document');
formData.append('category', 'reports');

const response = await ApiService.post('/api/upload', formData);

if (response.ok) {
  console.log('Upload successful:', response.data.url);
}

8. Search with Debouncing

let searchTimeout;

function handleSearch(query) {
  // Clear previous timeout
  clearTimeout(searchTimeout);

  // Abort previous request
  ApiService.abort('/api/search', { q: lastQuery });

  // Set new timeout
  searchTimeout = setTimeout(async () => {
    const response = await ApiService.get('/api/search', {
      q: query,
      limit: 10
    });

    displayResults(response.data);
  }, 300);  // Wait 300ms after user stops typing
}

searchInput.addEventListener('input', (e) => {
  handleSearch(e.target.value);
});

9. Conditional Caching

await ApiService.init({
  cache: {
    enabled: true,
    expiry: { get: 300000 },
    responsePredicate: (response) => {
      // Only cache successful responses with data
      return response.ok &&
             response.status === 200 &&
             response.data &&
             Object.keys(response.data).length > 0;
    }
  }
});

const response = await ApiService.get('/api/data');
// Will only be cached if response is successful and has data

10. Error Handling

try {
  const response = await ApiService.get('/api/users');

  if (response.ok) {
    console.log('Success:', response.data);
  } else {
    // Handle HTTP errors
    switch (response.status) {
      case 401:
        // Redirect to login
        window.location.href = '/login';
        break;

      case 403:
        alert('Access denied');
        break;

      case 404:
        console.error('Resource not found');
        break;

      case 500:
        alert('Server error. Please try again later.');
        break;

      default:
        console.error('Error:', response.statusText);
    }
  }
} catch (error) {
  // Handle network errors
  console.error('Network error:', error);
  alert('Cannot connect to server. Please check your internet connection.');
}

API Reference

Initialization

ApiService.init(options?: ConfigOptions): Promise<ApiService>

HTTP Methods

get(url: string, params?: object, options?: RequestOptions): Promise<Response>
post(url: string, data: any, options?: RequestOptions): Promise<Response>
put(url: string, data: any, options?: RequestOptions): Promise<Response>
delete(url: string, options?: RequestOptions): Promise<Response>

Authentication

setAccessToken(token: string): void
clearAccessToken(): void

Cache Management

clearCache(): void
invalidateCache(url: string, params?: object): boolean
invalidateCacheByUrl(url: string): number
createCacheKey(url: string, params?: object, method?: string): string

Request Control

abort(url: string, params?: object): boolean

Advanced Methods

all(requests: Promise[]): Promise<any[]>
poll(url: string, params?: object, interval?: number, callback: Function, options?: PollOptions): Function
sequence(requests: RequestConfig[], processor?: Function): Promise<any[]>

Utility Methods

buildUrlWithParams(url: string, params?: object): string

Best Practices

1. ✅ Initialize Once

// ❌ Bad: Initialize every time
async function fetchData() {
  await ApiService.init({ /* config */ });
  return await ApiService.get('/api/data');
}

// ✅ Good: Initialize once
await ApiService.init({ /* config */ });

async function fetchData() {
  return await ApiService.get('/api/data');
}

2. ✅ Use Appropriate Cache Expiry

// ❌ Bad: Cache everything the same
await ApiService.init({
  cache: {
    enabled: true,
    expiry: { get: 300000 }  // 5 minutes for everything
  }
});

// ✅ Good: Cache by data type
await ApiService.init({
  cache: {
    enabled: true,
    expiry: {
      get: 300000,  // 5 minutes for normal data
      post: 0,      // Don't cache POST
      put: 0,
      delete: 0
    }
  }
});

// ✅ Better: Disable cache per request
const realtimeData = await ApiService.get('/api/live-stats', {}, {
  cache: { enabled: false }
});

3. ✅ Use Hybrid Auth Strategy

// ❌ Bad: Storage strategy (insecure)
await ApiService.init({
  security: {
    bearerAuth: true,
    authStrategy: 'storage'  // Token in localStorage
  }
});

// ✅ Good: Hybrid strategy (more secure)
await ApiService.init({
  security: {
    bearerAuth: true,
    authStrategy: 'hybrid',  // Token in memory + HttpOnly cookie
    jwtRefresh: true
  }
});

4. ✅ Handle Errors Properly

// ❌ Bad: Don't handle errors
const response = await ApiService.get('/api/data');
console.log(response.data);  // May be undefined

// ✅ Good: Check response.ok
const response = await ApiService.get('/api/data');
if (response.ok) {
  console.log(response.data);
} else {
  console.error('Error:', response.statusText);
}

// ✅ Better: Use try-catch
try {
  const response = await ApiService.get('/api/data');
  if (response.ok) {
    console.log(response.data);
  }
} catch (error) {
  console.error('Network error:', error);
}

5. ✅ Enable CSRF Protection

// ✅ Good: Enable CSRF protection
await ApiService.init({
  security: {
    csrfProtection: true
  }
});

6. ✅ Use Retry Mechanism

// ✅ Good: Enable retry
await ApiService.init({
  connection: {
    retryOnNetworkError: true,
    maxNetworkRetries: 3,
    exponentialBackoff: true
  }
});

7. ✅ Cleanup Polling

// ❌ Bad: No cleanup
ApiService.poll('/api/status', {}, 5000, callback);

// ✅ Good: Store stop function
const stopPolling = ApiService.poll('/api/status', {}, 5000, callback);

// Cleanup when no longer needed
window.addEventListener('beforeunload', () => {
  stopPolling();
});

8. ✅ Abort Long Requests

// ✅ Good: Abort unwanted requests
const controller = new AbortController();

button.addEventListener('click', () => {
  ApiService.get('/api/large-data', {}, {
    signal: controller.signal
  });
});

cancelButton.addEventListener('click', () => {
  ApiService.abort('/api/large-data');
});

9. ✅ Use Deduplication

// ✅ Good: Enable deduplication (default)
await ApiService.init({
  deduplicate: true
});

// Prevent multiple clicks
button.addEventListener('click', async () => {
  const response = await ApiService.get('/api/data');
  // Multiple clicks will share the same request
});

10. ✅ Clear Cache Appropriately

// ❌ Bad: Never clear cache
await ApiService.post('/api/users', userData);
// Old cache still exists

// ✅ Good: ApiService clears cache automatically
await ApiService.post('/api/users', userData);
// Cache for '/api/users' is automatically removed

// ✅ Better: Clear cache manually when needed
await ApiService.post('/api/settings', settings);
ApiService.invalidateCacheByUrl('/api');  // Clear all /api cache

Summary

When to Use ApiService

Use Case Use ApiService?
Data that doesn't change often (categories, config) ✅ Yes - enable caching
Real-time data (stock prices, live stats) ❌ No - use http or disable cache
Need authentication ✅ Yes - has auth management
Need retry logic ✅ Yes - has retry mechanism
Prevent duplicate clicks ✅ Yes - has deduplication
Simple API call ❌ Not needed - use simpleFetch or http
File download/upload ✅ Can use - supports FormData
Polling ✅ Yes - has built-in polling
Performance tracking ✅ Yes - has tracking system

Key Features

Feature Available Note
Caching Memory, Session, Local storage
Deduplication Prevent duplicate requests
Retry With exponential backoff
Authentication Bearer, Basic, OAuth, Hybrid
CSRF Protection Automatic
JWT Refresh Auto-refresh before expiry
Polling With conditions
Abort Cancel pending requests
Tracking Performance & errors
Logging Multiple levels