曦远 (DealiAxy)一份私人的文字刊物

StarBlog
返回文章列表折页手记阅读全文
Web前端

Using Axios to encapsulate backend API calls in a TypeScript project

Wrapping axios? Under TypeScript, it's just adding some type declarations—calling that innovation is a stretch. Interceptors for automatic token addition and 401 redirects? That's just standard procedure. Unified return values? Back to that backend routine again, what a hassle.

曦远 (DealiAxy)约 19 分钟
目录10 个章节

Preface

Originally, I wanted to share my Next.js development notes, but then I realized they involve too many things, so I decided to split them up~

This article documents the process of encapsulating axios in a TypeScript project. Previously, while developing StarBlog-Admin, I had already done an encapsulation, but that was in JavaScript, which is somewhat different from TypeScript.

Additionally, while following the Next.js documentation for development, I noticed that the official docs recommend using @tanstack/react-query to encapsulate request operations. After a quick glance at the docs, it looks quite promising, and I plan to implement it in my project.

Define Configuration

First, create a global configuration file: src/utilities/global.ts

export default class Global {
  static baseUrl = process.env.NEXT_PUBLIC_BASE_URL
}

This is in a Next.js project, so you can use the environment variables specified by Next. For other projects, you can adjust this as needed.

Encapsulation Auth

The authentication part is somewhat related to axios, but not very much. However, since it's needed in the axios encapsulation, I'll include it here as well.

Create the src/utilities/auth.ts file

/**
 * Login Information
 */
export interface LoginProps {
  token: string
  username: string
  expiration: string
}

/**
 * Authentication Authorization Utility Class
 */
export default abstract class Auth {
  static get storage(): Storage | null {
    if (typeof window !== 'undefined') {
      return window.localStorage
    }
    return null
  }

  /**
     * Check if logged in
     * @return boolean
     */
  public static isLogin() {
    let token = this.storage?.getItem('token')
    let userName = this.storage?.getItem('user')

    if (!token || token.length === 0) return false
    if (!userName || userName.length === 0) return false
    return !this.isExpired();
  }

  /**
     * Check if login has expired
     * @return boolean
     */
  public static isExpired = () => {
    let expiration = this.storage?.getItem('expiration')
    if (expiration) {
      let now = new Date()
      let expirationTime = new Date(expiration)
      if (now > expirationTime) return true
    }

    return false
  }

  /**
     * Read saved token
     * @return string
     */
  public static getToken = () => {
    return this.storage?.getItem('token')
  }

  /**
     * Save login information
     * @param props
     */
  public static login = (props: LoginProps) => {
    this.storage?.setItem('token', props.token)
    this.storage?.setItem('user', props.username)
    this.storage?.setItem('expiration', props.expiration)
  }

  /**
     * Logout
     */
  public static logout = () => {
    this.storage?.removeItem('token')
    this.storage?.removeItem('user')
    this.storage?.removeItem('expiration')
  }
}

I've placed all authentication-related logic in the Auth class.

To work smoothly in Next.js, some special handling is required. For example, I added a storage property that checks for the existence of window before reading.

Encapsulating Axios

All code related to the API is placed in the src/services directory.

Create the src/services/api.ts file. The code is quite long, so it will be introduced in sections. As you can see, all configurations have additional settings compared to the previous JavaScript version, making them very friendly for IDE auto-completion.

First, import the necessary modules.

import axios, {AxiosInstance, AxiosRequestConfig, AxiosResponse, CreateAxiosDefaults} from "axios";
import Global from '@/utilities/global'
import Auth from "@/utilities/auth";

Axios Configuration

Define the configuration for axios.

const config: CreateAxiosDefaults<any> = {
  method: 'get',
  // Base URL prefix
  baseURL: `${Global.baseUrl}/`,
  // Request header information
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  },
  // Parameters
  data: {},
  // Set the timeout duration
  timeout: 10000,
  // Send credentials
  withCredentials: true,
  // Response data type
  responseType: 'json'
}

Standardized API Responses

Setting up standardized API responses - this is the same pattern I implemented in the StarBlog backend. It has become my standard approach for backend responses. I've also released the CodeLab.Share NuGet package, which allows quick integration of this standardized response component.

// Standardized API Responses
export interface ApiResponse {
  data: any
  errorData: any
  message: string
  statusCode: number
  successful: boolean
}

Define ApiClient

Finally, we define the ApiClient class, which is a bit reminiscent of C#'s HttpClient.

It uses Axios interceptors to add authentication information to the headers when making requests and to check for errors in responses. If there is a 401 Unauthorized error, it redirects to the login page.

export class ApiClient {
  private readonly api: AxiosInstance

  constructor() {
    this.api = axios.create({
      ...config,
    })

    this.api.interceptors.request.use(
      config => {
        config.headers.Authorization = `Bearer ${Auth.getToken()}`
        return config
      },
      error => {
        return error
      })

    this.api.interceptors.response.use(
      response => {
        return response
      },
      error => {
        let reason = error
        if (error && error.response) {
          if (error.response.data) {
            reason = error.response.data
            if (!reason.message) reason.message = error.message
          }
          if (error.response.status === 401) {
            location.href = '/login'
          }
        }

        return Promise.reject(reason)
      }
    )
  }

  public request(options: AxiosRequestConfig): Promise<ApiResponse> {
    return new Promise((resolve, reject) => {
      this.api(options).then((res: AxiosResponse<ApiResponse>) => {
        resolve(res.data)
        return false
      }).catch(error => {
        reject(error)
      })
    })
  }
}

export const api = new ApiClient()

export default api

The code is simpler than what I had in StarBlog-Admin. I aim to implement the required functionality with as little code as possible.

Writing Specific Interface Calls

All my interface calls are written as services (this follows backend development thinking).

Here, using the SMS sending interface as an example.

Create the src/services/common.ts file, import the ApiClient object from the api.ts file we just defined, and simply call the request method.

The parameter type is AxiosRequestConfig, and I'm not making any modifications to axios itself, which I feel is more comfortable than using the customized interface from Antd Pro previously.

import {api} from './api'

export class SmsChannel {
  static local = 0
  static aliyun = 1
  static tencent = 2
}

export default abstract class CommonService {
  public static getSmsCode(phone: string, channel: number = SmsChannel.local) {
    return api.request({
      url: `api/common/getSmsCode`,
      params: {phone, channel}
    })
  }
}

Summary

After this encapsulation, it's much more comfortable than the previous StarBlog-Admin. Unfortunately, that earlier project used vue2.x and seems to not support TypeScript.

That's it for now; most of the content is still in the next.js development notes.

References

_EOF_

Using Axios to encapsulate backend API calls in a TypeScript project

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

配置Chrome支持网页内的frame跨域

前言 跨域限制可以保证安全,但是调试的时候关掉会更方便,然而现在网络上能找到的关闭跨域限制方法,在新版的Chrome浏览器上根本没用…… 经过一番摸索,发现用旧版的Chrome就可以绕过跨域限制,刚好我做的这个需要跨域内嵌frame的网页只是单纯展示的,所以这个正好符合我的需求,奥利给! 然后我找到了旧版的Chrome软件,解压出来是绿色版的可以直接运行,不过运行后发现直接把系统的Chrome给调

2021-12-18 · Web前端 · 约 1 分钟

React + TypeScript + Taro前端开发小结

前言 项目到一段落,先来记录一下,本文以前端新手的角度记录React、TypeScript、Taro相关技术的开发体验以及遇到的问题和解决方法。 之前总说要学React(这篇博客:代码使我头疼之React初学习),这次项目需要做H5前端+小程序,我终于能用上React了~ 使用React的开发框架之前就听过京东的Taro,所以就这个了,直接开码。 关于React 不错,感觉比Vue的模板写法自由很

2022-04-22 · Web前端 · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…