Maxios is a library based on Axios for making data requests. Its main function is to layer Axios configurations and merge them using the most common logic (merge/join/replace, etc.).
Although there are countless similar libraries on the market, I haven't encountered any that can manage various common request configurations in a layered manner during daily development. For example, when you want to handle errors, if you use Axios directly, you might use its interceptors for global error handling (e.g., error prompts). However, in some special scenarios, you might want certain requests to handle errors independently without triggering the global error logic. Or you may want to add specific error-handling logic for certain modules while ensuring that the global error logic works as expected. If you want to manage common request configurations in a layered way and control whether upper-layer configurations are effective at any time, Maxios is your best choice.
You can install Maxios via Yarn or NPM:
# npm
npm install @awey/maxios
# yarn
yarn add @awey/maxiosThen, import Maxios into your code:
import { globalConfig, modulize } from '@awey/maxios'
globalConfig({
baseUrl: '/api'
})
const request = modulize({
baseUrl: '/user'
})
request().success(users => {/* Business logic */})Maxios recommends organizing your requests into different model files after completing the global request configuration. Then, import the models in your business code and use the methods within them to initiate requests.
First, configure the global request:
/* global-config.ts */
import { globalConfig } from '@awey/maxios'
globalConfig({
baseUrl: '/api'
}, {
requestError (err) {/* handle error */}, // Logic for handling request errors
expect: response => response.data.code === 0, // Whether the server response meets expectations
error (data) {/* handle error */}, // Logic for handling unexpected server responses
extractor: response => response.data.data, // How to extract business data from AxiosResponse when the request is as expected
// ...other configurations
})Then create model files to categorize requests:
/* models/user.ts */
interface User {
id: number
name: string
age: number
gender: 'male' | 'female'
}
type UserInfo = Pick<User, 'name', 'age', 'gender'>
interface ResponseData<T> {
code: number
msg: string
data: T
}
const request = modulize({
baseURL: '/user'
})
// It is recommended to expose the entire model's interface as an object
export default Object.freeze({
getUsers (condition: Partial<UserInfo>) {
return request<void, ResponseData<User[]>>({
params: condition
})
},
getUserById (id: number) {
return request<void, ResponseData<User>>({
url: `/${id}`
})
}
})
// Alternatively, each interface can be returned independently
export const createUser = (userInfo: UserInfo) => {
return request<UserInfo, ResponseData<User>>({
method: 'POST',
data: userInfo
})
}Finally, in your business code, import the model you defined to write business logic. Maxios can be used directly in any framework without requiring hooks. Below are examples for both traditional chained calls and optional hooks:
Using Chained Calls (works in any framework):
/** Business code */
import { useState } from 'react'
import userModel, { User } from 'model/user'
const [usersLoading, setUsersLoading] = useState(false)
const [users, setUsers] = useState<User[]>([])
// Returning the model as an object is recommended because it enhances the readability of your business code
// Also, your methods can be named more concisely with the model prefix
userModel.getUsers({ name: 'Tony'})
.loading(setUsersLoading)
.success(setUsers)Using React Hook (optional):
If you prefer a hook-based approach, Maxios provides optional hooks for React. See the Hooks section for detailed usage and features.
Using Vue Hook (optional):
If you prefer a hook-based approach, Maxios provides optional hooks for Vue. See the Hooks section for detailed usage and features.
The request() function returned by modulize() returns an object that supports chained calls. As shown in the code above, you can use methods like loading(), success(), etc., in a chained manner after getUsers(), making your code more concise and elegant.
This object that supports chained calls provides the following methods for you to use:
loading: This method accepts a callback function that is called when the loading state of the request changes; the callback function accepts abooleantypestatusparameter to indicate the current loading state; it can returnfalseto prevent higher-level callback functions from executingrequestError: This method accepts a callback function that is called when a request error occurs; the callback function accepts anAxiosErrortypeerrorparameter to indicate the current error information; it can returnfalseto prevent higher-level callback functions from executingerror: This method accepts a callback function that is called when theexpectin Maxios configuration returnsfalse; it accepts adataparameter of typeOriginResultto indicate the original data returned by the server; it can returnfalseto prevent higher-level callback functions from executingsuccess: This method accepts a callback function that is called when theexpectin Maxios configuration returnstrue; it accepts adataparameter of typeFinalResultto indicate the data extracted from the server's response by theextractorin Maxios configuration; it can returnfalseto prevent higher-level callback functions from executinganyway: This method accepts a callback function that is always called regardless of what happens with the request; the callback function has two parameters, the first parameterresultrepresents the result of the request, which may be anAxiosResponseor anAxiosError, and the second parameterconfigrepresents the final configuration information used for the current request; it can returnfalseto prevent higher-level callback functions from executingabort: This method is used to cancel the request
Maxios provides the following APIs:
globalConfig(axiosConfig, MaxiosConfig): This method is used to set global request configurations, the first parameter is the configuration passed to Axios, and the second parameter is the configuration passed to Maxiosmodulize(axiosConfig, maxiosConfig): This method is used to obtain a modular request method, the first parameter is the configuration passed to Axios, and the second parameter is the configuration passed to Maxios, it returns arequest(axiosConfig, maxiosConfig)methodrequest(axiosConfig, maxiosConfig): The method returned bymodulize(), used to initiate requests; it accepts the same parameters asglobalConfig()andmodulize(), the first parameter is the configuration passed to Axios, and the second parameter is the configuration passed to Maxios; it returns a chained call object introduced earlierrace(requests): This method is used to make multiple requests in a race condition, using the result of the first returned request as its result; it accepts an array composed of chained objects returned by therequest()method mentioned earlier; it also returns a chained call objectall(requests): This method is used to initiate multiple requests simultaneously and use the results of all requests as its result; it accepts an array composed of chained objects returned by therequest()method mentioned earlier; it also returns a chained call objectuseMaxios(requestFn, options?): Available in both React and Vue versions. See the Hooks section for detailed usage and features.
It should be noted that to get a complete type hint experience, you need to specify specific types for its generics when calling the request method. It accepts three generics:
Payload: The type of payload data for the request, i.e., the type of data attached to thebodyof the requestOriginResult: The data type of the original result returned by the requestFinalResult: The data type of the result returned by the request after being extracted by theextractor(theextractorwill be explained in detail later)
It is worth noting that all places that accept axiosConfig and maxiosConfig parameters also accept a function that returns a configuration object as a parameter. For example:
globalConfig(() => ({
baseUrl: 'api',
headers: {
token: localStorage.getItem('auth-token')
}
}))When some configurations need to be dynamically obtained for each request, using functional configuration is a better choice.
For
axiosConfig, please refer to the Axios official website.
As mentioned earlier, you can manage Axios configuration information for the same request at different levels through different Maxios APIs. This is also the core purpose and function of Maxios.
Axios has many configuration items, but in general, Maxios follows the strategy of "the lower the level, the higher the priority" to merge configurations at each level. Specifically, it is request > modulize > globalConfig. Most configurations in the Axios configuration object will be directly replaced (replace) by higher-priority configurations with the same name, but some special configurations use different merging strategies in Maxios.
baseURL: ThebaseURLconfigured at different levels will use the "Path Join" strategy, with higher levels in front and lower levels behind. For example, if/apiis configured inglobal,/settinginmodule, and/roleinrequest, the final request'sbaseURLwill be/api/setting/roleheaders: Theheadersobjects configured at all levels will be merged into one object before the final request through a method similar toObject.assignparams: Theparamsobjects configured at all levels will be merged into one object before the final request through a method similar toObject.assigndata: Thedataobjects configured at all levels will be merged into one object before the final request through a method similar toObject.assign; however, it should be noted that ifdatais not a simple object at any level, the final request will use that configuration
The second parameter maxiosConfig of globalConfig, modulize, and request represents the configuration object provided to Maxios. Maxios supports the following common configurations:
requestError: AxiosError => boolean | void: When a request error occurs (unable to obtain response data), you may want to handle it (e.g., prompt the user that the request failed), you can userequestErrorto configure a callback function for handling; the callback function accepts anAxiosErrorrepresenting the request error information as the only parameter; the chained call object mentioned earlier also provides a method with the same name, which has the same effectexpect: AxiosResponse => boolean: A callback function used to determine whether the response returned by the request meets expectations. If it returnsfalse, the response is considered not to meet expectations, and Maxios will call the callback function configured inerror()below; if it returnstrue, the response is considered to meet expectations, and the callback function configured insuccesswill be called; the callback function accepts anAxiosResponserepresenting the response information as the only parametererror: OriginResult => boolean | void: When the request returns data that does not meet expectations, if you need to handle this situation, you can useerrorto configure some callback functions for handling; the callback function accepts anOriginResultrepresenting the original response data (obtained from the response information object) as the only parameter; the chained call object mentioned earlier also provides a method with the same name, which has the same effectextractor: OriginResult => FinalResult: When the request returns data that meets expectations, there may be some common structures in the outermost layer of the data, and thesuccessprocessing logic may not care about these common structures. At this time, you can useextractorto strip the outer structure and extract business data; the callback function accepts anOriginResultparameter representing the original return data, and it needs to return the extracted business data; the default extraction logic is to directly extract the original response datasuccess: FinalResult => boolean | void: When the request returns data that meets expectations, your subsequent business logic can usesuccessfor processing; the chained call object mentioned earlier also provides a method with the same name, which has the same effectloading: Boolean => boolean | void: This configuration is used to indicate the loading state of the request; when the loading state of the request changes, this callback function will be called; the callback function accepts abooleanrepresenting whether it is currently loading as the only parameter; the chained call object mentioned earlier also provides a method with the same name, which has the same effectanyway: (AxiosResponse | AxiosError) => boolean | void: Regardless of the result of the request, this callback function will always be called after the request ends; the callback function accepts a single parameter to represent the response information or response error information; the chained call object mentioned earlier also provides a method with the same name, which has the same effect
The following configurations follow the strategy of "the lower the level, the higher the priority", and after the lower-level configuration, the same name configuration of the upper level will not take effect:
expectextractor
The following configurations will take effect at all levels, and the execution order is from low to high; if the callback function of the lower level does not want the callback of the upper level to continue executing, it can interrupt the execution of subsequent higher-level callback functions by returning false:
requestErrorerrorsuccessloadinganyway
It is worth noting that for these configurations (callback functions) that will be executed at all levels, there are actually four levels (the lowest level is the chained call object mentioned earlier), and these levels are from high to low:
globalConfigmodulizerequest- Chained call object
Maxios also provides some advanced configurations. These configurations correspond to functions that are not commonly used, but once you have the corresponding needs, they will perfectly help you handle all the dirty work.
Maxios provides a request retry function. You can specify conditions under which to retry requests. This feature is particularly useful in certain scenarios, such as when you start using a dual-token (Access Token / Refresh Token) mechanism to provide seamless renewal functionality for user authentication, Maxios's retry feature can come in handy.
retryWhen: Request retry logicretryWhen.requestSuccess: Retry logic after a successful requestretryWhen.requestSuccess.beforeRetry?: () => Promise | boolean | void: Logic to be executed before each retry. IfPromise.reject()orfalseis returned, the subsequent retry logic will be stoppedretryWhen.requestSuccess.condition?: AxiosResponse => boolean: Retry conditionretryWhen.requestSuccess.retryOthers?: boolean | 'module' | 'global': Whether to cancel and retry other ongoing requests during retryretryWhen.requestSuccess.maximumCount?: number: Maximum number of retries
retryWhen.requestError: This configuration represents the retry logic after a request failureretryWhen.requestError.beforeRetry?: () => Promise | void: Logic to be executed before each retryretryWhen.requestError.condition?: AxiosError => boolean: Retry conditionretryWhen.requestError.retryOthers?: boolean | 'module' | 'global': Whether to cancel and retry other ongoing requests during retryretryWhen.requestError.maximumCount?: number: Maximum number of retries, default is 1
Maxios provides a request result caching function. If some requests return data with a very low update frequency and are requested frequently across the client, you can consider using result caching to reduce the number of real requests. The related configurations for request result caching are as follows:
cache: Request result caching logiccache.type: Specify the type of cache to use, which can bememory,session(window.sessionStorage), orlocal(window.localStorage)cache.key: Cache key, used to identify and retrieve cached results
Maxios uses axios.request() by default to initiate requests. In some cases (such as when you want to add some Ajax probes to your application to collect performance data and error information of Ajax requests), you may want to customize the request method. You can use the maxiosConfig.request configuration to let Maxios use the request method you provide to initiate requests. The type signature of this configuration is as follows:
type TRequest = <T = unknown, R = AxiosResponse<T>, D = any> (config: AxiosRequestConfig<D>) => Promise<R>If you are upgrading from V1 to V2, you can check the following checklist for the changes you need to make:
global()is renamed toglobalConfig(), and the parameters have changed from one to two, withaxiosConfigseparated frommaxiosConfigas the first parameter- The parameters of
modulize()andrequest()have changed from one to two, withaxiosConfigseparated frommaxiosConfigas the first parameter - The callback function configuration for request errors
maxiosConfig.erroris renamed tomaxiosConfig.requestError, and the return value for interrupting subsequent level execution has changed fromtruetofalse - The function for determining whether the response meets expectations
indicatoris renamed toexpect - The callback function configuration for unexpected response
maxiosConfig.bizErroris renamed tomaxiosConfig.error, and the return value for interrupting subsequent level execution has changed fromtruetofalse - The excution order of callback functions
loading,successandanywayhas been changed to 'down-to-up', and add add the ability of interruption subsuquent level execution by returnfalse
Maxios can be used directly in any framework without requiring hooks. However, if you prefer a hook-based approach, Maxios provides optional hooks for React and Vue applications to simplify data fetching in component-based frameworks. The hooks automatically manage request state (data, loading, error) and provide flexible control over when requests are triggered.
Note: Hooks are completely optional. You can use Maxios with its chained call API in any framework, including React and Vue, without importing any hooks.
- React: Import
useMaxiosfrom@awey/maxios/react. Requires React >= 16.8.0 as a peer dependency. - Vue: Import
useMaxiosfrom@awey/maxios/vue. Requires Vue >= 3.0.0 as a peer dependency.
React:
import { useMaxios } from '@awey/maxios/react'
import userModel from 'model/user'
function UserList() {
const { request, data: users, loading, error } = useMaxios(
userModel.getUsers,
{ args: [{ name: 'Tony' }] }
)
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {JSON.stringify(error)}</div>
return (
<div>
{users?.map(user => <div key={user.id}>{user.name}</div>)}
</div>
)
}Vue:
<script setup lang="ts">
import { useMaxios } from '@awey/maxios/vue'
import userModel from 'model/user'
// Note: data, loading, and error are Vue refs
const { request, data: users, loading, error } = useMaxios(
userModel.getUsers,
{ args: [{ name: 'Tony' }] }
)
</script>
<template>
<div>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ JSON.stringify(error) }}</div>
<div v-else>
<div v-for="user in users" :key="user.id">{{ user.name }}</div>
</div>
</div>
</template>The auto option controls when requests are automatically triggered:
// Auto-trigger on mount and when args change (default)
const { data } = useMaxios(api.getUsers, {
args: [userId],
auto: true
})
// Disable auto-triggering
const { request, data } = useMaxios(api.getUsers, {
args: [userId],
auto: false
})
// Conditional auto-triggering with function
const { data } = useMaxios(api.getUsers, {
args: [userId],
auto: () => userId > 0
})For more fine-grained control, you can use an object format:
const { data } = useMaxios(api.getUserById, {
args: [userId],
auto: {
enable: isEnabled, // Enable/disable auto-triggering
condition: () => userId > 0, // Additional condition function
debounce: true // Debounce args changes (300ms default)
}
})Object format options:
enable?: boolean- Enable or disable auto-triggering (defaults totrueif not provided)condition?: () => boolean- Additional condition function that must returntruefor the request to triggerdebounce?: boolean | number- Debounce args changes:false(default) - No debouncingtrue- 300ms debouncenumber- Custom debounce delay in milliseconds
Arguments (args) are reactive and will automatically trigger requests when they change:
// React
const [userId, setUserId] = useState(1)
const { data } = useMaxios(api.getUserById, {
args: [userId], // Request triggers when userId changes
auto: true
})
// Vue
const userId = ref(1)
const { data } = useMaxios(api.getUserById, {
args: computed(() => [userId.value]), // Request triggers when userId changes
auto: true
})The returned request function remembers the latest args and allows parameter override:
const { request, data } = useMaxios(api.getUserById, {
args: [userId],
auto: false
})
// Use remembered args
request()
// Override with new params
request(999)Debouncing helps reduce unnecessary requests when arguments change frequently:
// 300ms debounce (default when debounce: true)
const { data } = useMaxios(api.searchUsers, {
args: [searchQuery],
auto: {
debounce: true
}
})
// Custom debounce delay
const { data } = useMaxios(api.searchUsers, {
args: [searchQuery],
auto: {
debounce: 500 // 500ms debounce
}
})Note: Debouncing only applies when args change, not when condition or enable change.
useMaxios<TRequestFn>(
requestFn: TRequestFn,
options?: {
args?: ExtractRequestArgs<TRequestFn>,
auto?: boolean | (() => boolean) | {
enable?: boolean
condition?: () => boolean
debounce?: boolean | number
}
}
): {
data: FinalResult | undefined
loading: boolean
error: OriginResult | AxiosError | undefined
request: (...args: RequestArgs) => IProcessorsChain
}Return values:
data- The extracted response data (fromextractor).undefinedin React,Ref<undefined>in Vue.loading- Loading state.booleanin React,Ref<boolean>in Vue.error- Error information (request error or business error).undefinedin React,Ref<undefined>in Vue.request- Function to manually trigger the request. Can be called with new arguments to override remembered args.
Note: In Vue, data, loading, and error are reactive refs, so you need to access their .value property when needed (though Vue's template automatically unwraps refs).
The hook provides full TypeScript support with automatic type inference:
// TypeScript automatically infers types from requestFn
const { data } = useMaxios(api.getUserById, { args: [1] })
// data type: User | undefined
// For functions with optional parameters
const { data } = useMaxios(api.getUsers, { args: [{ name: 'Tony' }] })
// data type: User[] | undefined
// For functions with no parameters
const { data } = useMaxios(api.getAllUsers)
// data type: User[] | undefinedNote: Hooks are completely optional. You can use Maxios with its chained call API in any framework, including React and Vue, without importing any hooks. This helps avoid unnecessary framework dependencies in your bundle.