Comprehensive Guide to Encapsulating API Requests with Axios and TypeScript
This article explains why and how to wrap asynchronous API calls in a reusable TypeScript utility using Axios, covering request flow analysis, interceptor design, unified GET/POST/PUT/DELETE helpers, development order, and complete source code examples for a maintainable frontend architecture.
Introduction (Why Build)
In modern frontend projects, asynchronous API calls are unavoidable, and wrapping these calls becomes a mandatory skill. The author discovered that many teammates were using raw axios.post statements without any abstraction, which leads to duplicated code and maintenance headaches as the number of requests grows.
axios.post(`/api/xxxx/xxxx?xxx=${xxx}`, { ...data })
.then((result) => {
if (result.errno) {
// handle error
} else {
// handle success
}
})
.catch((err) => {
// handle network error
})Demo Effect
const [e, r] = await api.getUserInfo(userid)
if (!e && r) this.userInfo = r.data.userinfoThe final implementation aims to provide a clean, type‑safe API request tool that returns a tuple [err, result] for both success and failure cases.
Clear Thinking – What We Need to Do
Before coding, the author spent two hours drawing a request flow diagram to identify two major concerns:
Basic request process (pre‑intercept, actual request, post‑intercept)
Interceptors (request and response handling)
Basic Request Process
The flow can be split into three stages:
Before request interception
Actual HTTP request
After response interception
These stages further divide into handling a single endpoint and handling common logic for all endpoints (GET, POST, PUT, DELETE).
Interceptors
Two categories of interceptors are defined:
Request interceptor – adjust headers, attach authentication token, etc.
Response interceptor – unified error handling for network errors, authentication errors, and generic business errors.
Unified Call Order
The development follows a strict order to avoid uncertainty:
Common handling for all GET requests
Request interceptor
Response interceptor
Specific handling for individual endpoints
Packaging logic
Common handling for POST/PUT/DELETE
Implementation Details
GET Helper
type Fn = (data: FcResponse
) => unknown
interface IAnyObj { [index: string]: unknown }
interface FcResponse
{ errno: string; errmsg: string; data: T }
const get =
(url: string, params: IAnyObj = {}, clearFn?: Fn): Promise<[any, FcResponse
| undefined]> =>
new Promise((resolve) => {
axios
.get(url, { params })
.then((result) => {
let res: FcResponse
if (clearFn !== undefined) {
res = clearFn(result.data) as unknown as FcResponse
} else {
res = result.data as FcResponse
}
resolve([null, res])
})
.catch((err) => {
resolve([err, undefined])
})
})Request Interceptor
const handleRequestHeader = (config) => {
config['xxxx'] = 'xxx'
return config
}
const handleAuth = (config) => {
config.header['token'] = localStorage.getItem('token') || token || ''
return config
}
axios.interceptors.request.use((config) => {
config = handleRequestHeader(config)
config = handleAuth(config)
return config
})Response Interceptor and Error Handlers
const handleNetworkError = (errStatus) => {
let errMessage = '未知错误'
if (errStatus) {
switch (errStatus) {
case 400: errMessage = '错误的请求'; break
case 401: errMessage = '未授权,请重新登录'; break
case 403: errMessage = '拒绝访问'; break
case 404: errMessage = '请求错误,未找到该资源'; break
case 405: errMessage = '请求方法未允许'; break
case 408: errMessage = '请求超时'; break
case 500: errMessage = '服务器端出错'; break
case 501: errMessage = '网络未实现'; break
case 502: errMessage = '网络错误'; break
case 503: errMessage = '服务不可用'; break
case 504: errMessage = '网络超时'; break
case 505: errMessage = 'http版本不支持该请求'; break
default: errMessage = `其他连接错误 --${errStatus}`
}
} else {
errMessage = `无法连接到服务器!`
}
message.error(errMessage)
}
const handleAuthError = (errno) => {
const authErrMap = {
'10031': '登录失效,需要重新登录',
'10032': '您太久没登录,请重新登录~',
'10033': '账户未绑定角色,请联系管理员绑定角色',
'10034': '该用户未注册,请联系管理员注册用户',
'10035': 'code 无法获取对应第三方平台用户',
'10036': '该账户未关联员工,请联系管理员做关联',
'10037': '账号已无效',
'10038': '账号未找到',
}
if (authErrMap.hasOwnProperty(errno)) {
message.error(authErrMap[errno])
logout()
return false
}
return true
}
const handleGeneralError = (errno, errmsg) => {
if (errno !== '0') {
message.error(errmsg)
return false
}
return true
}
axios.interceptors.response.use(
(response) => {
if (response.status !== 200) return Promise.reject(response.data)
handleAuthError(response.data.errno)
handleGeneralError(response.data.errno, response.data.errmsg)
return response
},
(err) => {
handleNetworkError(err.response.status)
return Promise.reject(err.response)
}
)POST/PUT/DELETE Helpers
export const post =
(url: string, data: IAnyObj, params: IAnyObj = {}): Promise<[any, FcResponse
| undefined]> => {
return new Promise((resolve) => {
axios
.post(url, data, { params })
.then((result) => resolve([null, result.data as FcResponse
]))
.catch((err) => resolve([err, undefined]))
})
}
// Put / Del are analogousUnified Export
import { userApi } from "./path/user"
import { shoporderApi } from "./path/shoporder"
export const api = {
...userApi,
...shoporderApi,
}With this structure, developers can call any endpoint via api.getUserInfo(id) or api.getShoporderList() , benefiting from consistent error handling, type safety, and reduced boilerplate across the entire frontend codebase.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.