A business-focused modular encapsulate module based on axios.
Try this webpack project example with modular file splitting.
You can install the library via npm.
Note: the axios library is not included in the package, you need to install the axios dependency separately
npm i axios @calvin_von/axios-api-module -S
or via yarn:
yarn add axios @calvin_von/axios-api-module
or via CDN
<!-- You need import axios separately. -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@calvin_von/axios-api-module/dist/axios-api-module.min.js"></script>
Why? This design allows users to freely choose the appropriate axios version (please follow the semver version rule, and now we supports 0.x versions)
// You should import axios at first
import axios from 'axios';
import ApiModule from "@calvin_von/axios-api-module";
// or CDN import
// var ApiModule = window['ApiModule'];
// create a modular namespace ApiModule instance
const apiMod = new ApiModule({
baseConfig: {
baseURL: 'http://api.yourdomain.com',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
withCredentials: true,
timeout: 60000
},
module: true,
metadatas: {
main: {
getList: {
url: '/api/list/',
method: 'get',
// Add another custom fields
name: 'GetMainList'
}
},
user: {
getInfo: {
// support multiple params definitions
// url: '/api/user/:uid/info',
url: '/api/user/{uid}/info',
method: 'get'
name: 'getUserInfo',
}
}
}
});
// get the converted request instance
const apiMapper = apiMod.getInstance();
apiMapper.$module === apiMod; // true
// send request
// apiMapper is mapped by the passed metadatas option
apiMapper.main.getList({ query: { pageSize: 10, pageNum: 1 } });
apiMapper.user.getInfo({ params: { uid: 88 } });
You need to organize the interface into an object (or objects from multiple namespaces) and pass it into the metadatas option.
-
When the number of interfaces is not large, or if you want to instantiate more than one, set
module
tofalse
or empty value,ApiModule
will adopt a single namespaceconst apiModule = new ApiModule({ module: false, metadatas: { requestA: { url: '/path/to/a', method: 'get' }, requestB: { url: '/path/to/b', method: 'post' }, } // other options... });
Use the
#getInstance
method to get the request collection object after conversionconst apiMapper = apiModule.getInstance(); apiMapper .requestA({ query: { a: 'b' } }) .then(data => {...}) .catch(error => {...})
-
When
module
is set totrue
,ApiModule
will enable multiple namespacesconst apiModule = new ApiModule({ module: true, metadatas: { moduleA: { request: { url: '/module/a/request', method: 'get' }, }, moduleB: { request: { url: '/module/b/request', method: 'post' }, } } // other options... }); const apiMapper = apiModule.getInstance(); apiMapper .moduleA .request({ query: { module: 'a' } }) .then(data => {...}) .catch(error => {...}) apiMapper .moduleB .request({ body: { module: 'b' } }) .then(data => {...}) .catch(error => {...})
To send request, you need to use the ApiModule#getInstance method to get the converted request collection object, then just like this:
Request({ query: {...}, body: {...}, params: {...} }, opt?)
-
query: The URL parameters to be sent with the request, must be a plain object or an URLSearchParams object. axios params option
-
params: Support dynamic url params(usage likes vue-router dynamic matching)
-
body: The data to be sent as the request body. axios data option
-
opt: More original request configs available. Request Config
const request = apiMapper.user.getInfo;
// *configurable context parameter
console.log(request.context);
// axios origin request options
const config = { /* Axios Request Config */ };
const requestData = {
params: {
uid: this.uid
},
query: {
ts: Date.now()
}
};
// is equal to
axios.get(`/api/user/${this.uid}/info`, {
query: {
ts: Date.now()
}
});
In addition, each converted request has a context
parameter, which is convenient for setting various parameters of the request outside the middleware
const context = Request.context;
context.setAxoisOptions({ ... });
ApiModule
has a middleware mechanism, designed more fine-grained unified control around the requested before request, post request, andrequest failed stages to help developers better organize code
The recommended way is to define a custom field in the metadata that defines the interface, and then get and perform a certain operation in the corresponding middleware.
The following is an example of adding user information parameters before making a request and preprocessing the data after the request is successful:
const userId = getUserIdSomehow();
const userToken = getUserTokenSomehow();
apiModule.useBefore((context, next) => {
const { appendUserId, /** other custom fields */ } = context.metadata;
if (appendUserId) {
const data = context.data || {};
if (data.query) {
data.query.uid = userId;
}
context.setData(data);
context.setAxiosOptions({
headers: {
'Authorization': token
}
});
}
next(); // next must be called
});
apiModule.useAfter((context, next) => {
const responseData = context.response;
const { preProcessor, /** other custom fields */ } = context.metadata;
if (preProcessor) {
try {
context.setResponse(preProcessor(responseData));
} catch (e) {
console.error(e);
}
}
next();
});
In fact,
ApiModule
was originally designed to avoid writing bloated code repeatedly, thereby separating business code.
Moreover,
ApiModule
regards the interceptor provided by the axios as the "low-level" level affairs that encapsulates the browser request, also,ApiModule
designs the middleware pattern to handle the "business level" affairs. In fact, you can put each interface definition is treated as a data source service (something like the "Service" concept in Angular), and you can do some operations that are not related to the page, so it is called "a business-focused packaging module".
-
Type:
(context, next) => null
-
Parameters:
Each middleware contains two parameters:
-
context
- Type: Context
- Description: Provides a series of methods to modify request parameters, response data, error data, and request axios options, and provides a series of request-related read-only parameters.
-
next
- Type:
(error?: object | string | Error) => null
- Description:
- Each middleware must call the
next
function to proceed to the next step. - Passing the error parameters will cause the request to fail (the browser will not send a real request and will directly cause the request to be rejected in the fore-request middleware).
- Passing the error parameters using the Context#setError method behaves the same as the parameters passed in the
next
function.
- Each middleware must call the
- Type:
-
Multiple ApiModule
instances do not affect each other. Middleware set separately by the instance will override globally set middleware
- Set the fore-request middleware: ApiModule#useBefore
- Set the post-request middleware: ApiModule#useAfter
- Set the request failed middleware: ApiModule#useCatch
Setting the global middlewares will affect all ApiModule
instances created later
- Set the fore-request middleware: ApiModule.globalBefore
- Set the post-request middleware: ApiModule.globalAfter
- Set the request failed middleware: ApiModule.globalCatch
You can still set axios interceptors. Using ApiModule
will not affect the original interceptor usage.
You can use the ApiModule#getAxios method to export the axios
instance to set the interceptor
Execution order between
axios intercepters
andApiModule middlewares
- fore-request middleware
- axios request intercepter
- axios response intercepter
- post-request or fallback middleware
It can be seen that the execution of our business axios
is more "underlying", so we recommend that business-related code be implemented in the middleware, and the interceptor is only to determine whether the request is sent successfully or implements some protocol and framework related affairs.
const axiosInstance = apiMod.getAxios();
axiosInstance.interceptors.request.use(
function (config) {
return config;
},
function (error) {
return Promise.reject(error);
}
);
axiosInstance.interceptors.response.use(
function (response) {
if (response.data.status === 200) {
return response.data;
}
return Promise.reject(new Error(response.msg));
},
function (error) {
return Promise.reject(error);
}
);
const apiMod = new ApiModule({
baseConfig: { /*...*/ }, // Object, axios request config
module: true, // Boolean, whether modular namespace
console: true, // Boolean, switch log on off
metadatas: {
main: { // namespace module
getList: {
method: 'get', // request method "get" | "post" | "patch" | "delete" | "put" | "head"
url: '/api/user/list'
}
}
}
});
Set base axios request config for single api module.
More details about baseConfig, see Axios Doc(#Request Config)
Whether enable modular namespaces. Learn more.
Example in Vue.js:
You can create multiple instance, typically whenmodule
option set tofalse
Vue.prototype.$foregroundApi = foregroundApis;
Vue.prototype.$backgroundApi = backgroundApis;
Set the fore-request middleware, which is consistent with the definition of #useBefore, but will be overridden by the instance method and will affect all the ApiModule
instances
Set the post-request middleware, which is consistent with the definition of #useAfter, but will be overridden by the instance method and will affect all the ApiModule
instances
Set the request failed middleware, which is consistent with the definition of #useCatch, but will be overridden by the instance method and will affect all the ApiModule
instances
-
parameters:
foreRequestHook: (context, next) => null)
. Learn more about the Middleware Definition -
description:
The passed fore-request middleware will be called before every request. The available and effective
context
methods are as follows:If the wrong parameters are set at this time, the real request will not be sent, and the request will directly enter the failure stage.
-
parameters:
postRequestHook: (context, next) => null)
. Learn more about the Middleware Definition -
description:
The passed post-request middleware will be called after every request is successful. The available and effective
context
methods are as follows:If error parameters are set at this time, even if the request is successful, the request will enter the request failure stage
-
parameters:
fallbackHook: (context, next) => null)
. Learn more about the Middleware Definition -
description:
The passed request failed middleware will be called after each request fails (or is set incorrectly). The available and effective
context
methods are as follows:If an error parameter is set at this time, the original error value will be overwritten
- return:
TransformedRequestMapper | { [namespace: string]: TransformedRequestMapper, $module?: ApiModule };
- description: Get the mapped request collection object
const apiModule = new ApiModule({ /*...*/ }); const apiMapper = apiModule.getInstance(); apiMapper.xxx({ /* `query`, `body`, `params` data here */ }, { /* Axios Request Config */ });
- return:
AxiosInstance
- description: Get the axios instance that after setted
const apiModule = new ApiModule({ /*...*/ }); const axios = apiModule.getAxios(); axios.get('/other/path', { /* Axios Request Config */ });
-
return:
CancelTokenSource
-
description: Generate axios
Cancellation
source.You can use axios
cancellation
, (docs about axios#cancellation)import axios from 'axios'; const CancelToken = axios.CancelToken; const source = CancelToken.source(); ...
or just use
#generateCancellationSource()
... const api = apiMod.getInstance(); const cancelSourceA = api.$module.generateCancellationSource(); const cancelSourceB = api.$module.generateCancellationSource(); // send a request const requestA = api.test({ query: { a: 123 }, }, { cancelToken: cancelSourceA.token }); const requestB = api.test({ query: { b: 321 }, }, { cancelToken: cancelSourceB.token }); cancelSourceA.cancel('Canceled by the user'); // requestA would be rejected by reason `Canceled by the user` // requestB ok!
The copy of the metadata for the current request, that is, modifying the read-only value will not affect the original metadata
The object keys path array of metadata corresponding to the current request, for example, the request apiMapper.moduleA.interfaceB
method corresponds to ['moduleA','interfaceB']
.
Would be useful in the development environment.
Request method for the current request
The baseURL of the current request
The full request url path of the current request, a combination of baseURL
and parsed metadata.url
Request parameters for the current request, see details:
- data.query?: object.
URLSearchParams
query parameter for object request - data.params?: object. The dynamic URL parameters for the object request. Supports
/:id
and/{id}
definitions - data.body?: object. request body data
- Add other user-custom fields, which can be accessed in middlewares
Response data for the current request
The current request's response error data, or manually set error data, the existence of this value does not mean that the request must be failed
The axios
option parameter to be used in the current request will be obtained by combining the second opt
parameter and context#setAxiosOptions
passed in the request
Set the request parameters of the incoming request (View Details), which will overwrite the incoming data to achieve the purpose of overwriting the requested data
Set the requested response data, which will overwrite the original response to achieve the purpose of overwriting the successful data of the request
Set the request failure data, whether the request is successful or not, it will return failure
Set options for the axios
request, but will be merged with the axios
option passed in the request method, and the priority is not higher than the parameters passed in the request method