Configuring Axios with WebSockets and Fallback Polling

This article explains how to integrate the Axios HTTP client into real-time architectures that utilize WebSockets and fallback polling mechanisms. Because Axios is strictly an HTTP client that operates on standard request-response protocols, it cannot establish persistent WebSocket connections on its own. Instead, Axios plays a crucial role in authenticating real-time sessions, executing long or short polling when WebSockets are unsupported or blocked, and managing graceful degradation strategies.

The Role of Axios in Real-Time Architectures

WebSockets operate over the ws:// or wss:// protocols, providing full-duplex communication channels over a single TCP connection. Axios operates exclusively over http:// and https://.

In modern web applications, Axios complements WebSocket implementations in three primary ways:

  1. Authentication and Handshaking: Fetching short-lived session tokens or connection URLs via HTTP before initiating the WebSocket handshake.
  2. Fallback Polling: Handling short or long polling requests when firewalls, proxies, or legacy environments block WebSocket upgrades.
  3. Data Hydration: Fetching the initial state via standard REST endpoints before subscribing to incremental WebSocket updates.

Implementing Long Polling with Axios

When a WebSocket connection fails, long polling is the most common HTTP-based alternative. In long polling, the client sends an HTTP request to the server, which holds the request open until new data is available or a timeout occurs.

Axios Configuration for Long Polling

To implement reliable long polling, Axios must be configured with custom timeout thresholds and cancellation handling using AbortController.

import axios from 'axios';

class PollingService {
  constructor(endpoint, interval = 5000) {
    this.endpoint = endpoint;
    this.interval = interval;
    this.isPolling = false;
    this.abortController = null;
  }

  async startPolling(onDataReceived) {
    this.isPolling = true;

    while (this.isPolling) {
      this.abortController = new AbortController();

      try {
        const response = await axios.get(this.endpoint, {
          signal: this.abortController.signal,
          timeout: 30000, // Keep connection open for up to 30 seconds
          headers: {
            'Cache-Control': 'no-cache',
            'Pragma': 'no-cache'
          }
        });

        if (response.status === 200 && response.data) {
          onDataReceived(response.data);
        }
      } catch (error) {
        if (axios.isCancel(error)) {
          break; // Polling intentionally stopped
        }

        // Handle network errors or server timeouts
        console.error('Polling error, retrying...', error.message);
        await new Promise((resolve) => setTimeout(resolve, this.interval));
      }
    }
  }

  stopPolling() {
    this.isPolling = false;
    if (this.abortController) {
      this.abortController.abort();
    }
  }
}

export default PollingService;

Creating a Hybrid WebSocket and Axios Fallback Manager

A unified client abstraction allows an application to automatically attempt a WebSocket connection and fallback to Axios-based polling if the connection drops or fails.

import axios from 'axios';

class RealtimeClient {
  constructor(wsUrl, pollUrl) {
    this.wsUrl = wsUrl;
    this.pollUrl = pollUrl;
    this.socket = null;
    this.pollingActive = false;
    this.abortController = null;
    this.listeners = [];
  }

  subscribe(callback) {
    this.listeners.push(callback);
  }

  notify(data) {
    this.listeners.forEach((listener) => listener(data));
  }

  connect() {
    try {
      this.socket = new WebSocket(this.wsUrl);

      this.socket.onopen = () => {
        console.log('WebSocket connected');
        this.stopPollingFallback();
      };

      this.socket.onmessage = (event) => {
        const data = JSON.parse(event.data);
        this.notify(data);
      };

      this.socket.onerror = () => {
        console.warn('WebSocket error encountered. Switching to polling fallback.');
        this.socket.close();
      };

      this.socket.onclose = () => {
        if (!this.pollingActive) {
          this.startPollingFallback();
        }
      };
    } catch (e) {
      this.startPollingFallback();
    }
  }

  async startPollingFallback() {
    this.pollingActive = true;
    console.log('Fallback polling started via Axios');

    while (this.pollingActive) {
      this.abortController = new AbortController();

      try {
        const response = await axios.get(this.pollUrl, {
          signal: this.abortController.signal,
          timeout: 10000
        });

        if (response.data) {
          this.notify(response.data);
        }

        // Standard delay between short-polling cycles
        await new Promise((resolve) => setTimeout(resolve, 3000));
      } catch (error) {
        if (axios.isCancel(error)) {
          break;
        }
        await new Promise((resolve) => setTimeout(resolve, 5000));
      }
    }
  }

  stopPollingFallback() {
    this.pollingActive = false;
    if (this.abortController) {
      this.abortController.abort();
    }
  }

  disconnect() {
    if (this.socket) {
      this.socket.close();
    }
    this.stopPollingFallback();
  }
}

Essential Axios Settings for Fallback Strategies

When configuring Axios specifically for fallback operations, apply these settings: