Lesson 11-Vue3 API Interface Encapsulation

Encapsulating an API Request Library with Axios

http.js

// Encapsulation of an API request library based on Axios
import axios from 'axios';
import qs from 'qs'; // Import qs module for serializing POST data

// Set default API base URL based on environment
switch (process.env.NODE_ENV) {
  case 'production':
    axios.defaults.baseURL = 'http://api.zhufengpeixun.cn';
    break;
  case 'test':
    axios.defaults.baseURL = 'http://192.168.20.12:8080';
    break;
  default:
    axios.defaults.baseURL = 'http://127.0.0.1:3000';
}

// Set request timeout
axios.defaults.timeout = 10000;

// Allow CORS credentials
axios.defaults.withCredentials = true;

// Set POST request headers: Inform server of data format
axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.transformRequest = data => qs.stringify(data);

// Request interceptor
axios.interceptors.request.use(
  config => {
    removePending(config); // Cancel any identical pending requests
    addPending(config);
    // JWT token validation: Retrieve token from local storage and include in headers
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = token;
    }
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

// Response interceptor
axios.defaults.validateStatus = status => {
  // Define successful HTTP status codes
  return /^(2|3)\d{2}$/.test(status);
};

axios.interceptors.response.use(
  response => {
    removePending(response.config);
    // Return only the response data (some companies may further customize based on server response codes)
    return response.data;
  },
  error => {
    if (error.response) {
      switch (error.response.status) {
        case 400:
          error.message = 'Bad Request (400)';
          break;
        case 401: // Unauthorized, typically not logged in
          error.message = 'Unauthorized, please login (401)';
          break;
        case 403: // Server understood request but refused (often due to expired token)
          error.message = 'Forbidden (403)';
          localStorage.removeItem('token');
          // Redirect to login page
          break;
      }
      return Promise.reject(error.response);
    } else {
      // Handle network disconnection
      if (!window.navigator.onLine) {
        // Redirect to offline page if network is disconnected
        return;
      }
      return Promise.reject(error);
    }
  }
);

// Request cancellation
const pendingMap = new Map();

function getPendingKey(config) {
  const { url, method, params, data } = config;
  let requestBody = data || {};
  if (typeof requestBody === 'string') {
    requestBody = JSON.parse(requestBody); // Parse JSON string to object
  }
  return [url, method, JSON.stringify(params), JSON.stringify(requestBody)].join('&');
}

function addPending(config) {
  const pendingKey = getPendingKey(config);
  config.cancelToken =
    config.cancelToken ||
    new axios.CancelToken(cancel => {
      if (!pendingMap.has(pendingKey)) {
        pendingMap.set(pendingKey, cancel);
      }
    });
}

function removePending(config) {
  const pendingKey = getPendingKey(config);
  if (pendingMap.has(pendingKey)) {
    const cancel = pendingMap.get(pendingKey);
    cancel(pendingKey);
    pendingMap.delete(pendingKey);
  }
}

function clearPending() {
  for (const [url, cancel] of pendingMap) {
    cancel(url);
  }
  pendingMap.clear();
}

export default axios;

login.js

// API module for login
import axios from './http';

function login() {
  return axios.post('/login', {
    xxx: 'xxx',
  });
}

export default { login };

api.js

// Centralized entry point for API requests
import login from './login';

export default {
  login,
};

Encapsulating an API Request Library with Fetch

// Encapsulation of an API request library based on Fetch
import qs from 'qs';

/*
 * Differentiate API base URL based on environment
 */
let baseURL = '';
const baseURLArr = [
  {
    type: 'development',
    url: 'http://127.0.0.1:9000',
  },
  {
    type: 'test',
    url: 'http://192.168.20.15:9000',
  },
  {
    type: 'production',
    url: 'http://api.zhufengpeixun.cn',
  },
];

baseURLArr.forEach(item => {
  if (process.env.NODE_ENV === item.type) {
    baseURL = item.url;
  }
});

export default function request(url, options = {}) {
  url = baseURL + url;

  /*
   * Handle GET series requests
   */
  if (!options.method) {
    options.method = 'GET';
  }
  if (options.hasOwnProperty('params')) {
    if (/^(GET|DELETE|HEAD|OPTIONS)$/i.test(options.method)) {
      const ask = url.includes('?') ? '&' : '?';
      url += `${ask}${qs.stringify(options.params)}`;
    }
    delete options.params;
  }

  /*
   * Merge configuration options
   */
  options = Object.assign(
    {
      // Allow cross-origin credentials
      credentials: 'include', // 'same-origin' for same-origin only, 'omit' to reject all
      // Set request headers
      headers: {},
    },
    options
  );
  options.headers.Accept = 'application/json';

  /*
   * Token validation
   */
  const token = localStorage.getItem('token');
  if (token) {
    options.headers.Authorization = token;
  }

  /*
   * Handle POST requests
   */
  if (/^(POST|PUT)$/i.test(options.method)) {
    if (!options.type) {
      options.type = 'urlencoded';
    }
    if (options.type === 'urlencoded') {
      options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
      options.body = qs.stringify(options.body);
    }
    if (options.type === 'json') {
      options.headers['Content-Type'] = 'application/json';
      options.body = JSON.stringify(options.body);
    }
  }

  return fetch(url, options)
    .then(response => {
      // Handle non-200 status codes
      if (!/^(2|3)\d{2}$/.test(response.status)) {
        switch (response.status) {
          case 401: // Unauthorized, typically not logged in
            break;
          case 403: // Server understood but refused (often due to expired token)
            localStorage.removeItem('token');
            // Redirect to login page
            break;
          case 404: // Resource not found
            break;
        }
        return Promise.reject(response);
      }
      return response.json();
    })
    .catch(error => {
      // Handle network disconnection
      if (!window.navigator.onLine) {
        // Redirect to offline page if network is disconnected
        return;
      }
      return Promise.reject(error);
    });
}

Code Analysis

  • Axios Encapsulation: Provides a robust HTTP client with request/response interceptors, token validation, request cancellation, and environment-based base URLs. The http.js module centralizes configuration, login.js defines specific API endpoints, and api.js serves as the unified entry point.
  • Fetch Encapsulation: A lightweight alternative using the native Fetch API, supporting environment-based base URLs, token validation, and request type handling (GET/POST). It simplifies configuration but lacks advanced features like request cancellation compared to Axios.
  • Key Features: Both implementations handle CORS credentials, error handling (e.g., 401, 403), and network disconnection scenarios, ensuring reliable API interactions.

Share your love