Request Manager is a library for creating an sdk library for your server (API). The API type doesn't matter, whether it's REST or RPC. As a result, we get the opportunity to centrally process all requests. We do not bother with converting data in the code, but only work with pure data. It is better to see 1 time than to hear 100 times.
try {
let responseData = await RequestManager.Auth.authorization({login: 'admin', password: 'pass'})
// ... user code for success response
} catch (err) {
// ... user code for error response or server error
return;
}
RequestManager.Auth.authorization({login: 'admin', password: 'pass'}).then(
(result) => { /* ... user code for success response */ },
(error) => { /* ... user code for error response or server error */ }
);
NPM
# # install a request client, such as axios or fetch (or any other)
# npm install axios # for use axios client
# npm install node-fetch # for use fetch client in nodejs
# # install file download (if necessary)
# npm install js-file-download # for file download (or use other)
# # install
npm install js-request-manager
Yarn
# # install a request client, such as axios or fetch (or any other)
# yarn add axios # for use axios client
# yarn add node-fetch # for use fetch client in nodejs
# # install file download (if necessary)
# yarn add js-file-download # for file download (or use other)
# # install
yarn add js-request-manager
package.json
{
"dependencies": {
// "axios": "^0.21.1", or "node-fetch": "^2.6.1", or js fetch()
// js-file-download": "^0.4.12", or other
"js-request-manager": "^1.0.0",
// ..
}
}
- Copy optimal example files to your project (recommended).
- Copy minimal example file to your project.
- or create a file manually.
Example of manual creation
import RequestManager from 'js-request-manager/src/RequestManager'; import RequestClass from "js-request-manager/src/Class/RequestClass"; // request sender import axios from 'axios'; const requestSchema = { Auth: { authorization: ({login, password}) => { return new RequestClass({ name : 'authorization', type : 'POST', url : 'api://authorize', // https://domain.test/api/authorize params: { get: {}, post: {login, password}, }, responsePrepare: (data) => { return {token: data.jwt}; }, errorMessage: 'Not correct login or password', }); }, } } const Config = { hostSchema: { api: 'https://domain.test/api', }, Hook: { RequestPromise (requestPromise, settings) { console.log(requestPromise, settings); } }, RequestClient: { async send(obj) { return await axios(obj); } } } const RmSimpleCreate = RequestManager(requestSchema, Config); // optional (recommended use global request manager) global.RequestManager = RmSimpleCreate export default RmSimpleCreate
const RequestSchema = {/* ... request schema */ };
// Config - all parameters are optional
const Config = {
hostSchema: {},
RequestPrepare: {
data(requestType, requestUrl, requestData) {
return requestData;
},
type(requestType, requestUrl, requestData) {
return requestType;
},
url(requestType, requestUrl, requestData) {
return requestUrl.getUrl();
},
requestClientDataPrepare(requestClientData, requestClass) {
return requestClientData;
},
},
ResponsePrepare: {
isError(riObject) {
return false;
},
getErrorInfo: async (riObject, requestClass, Config) => {
return {code: 'error', message: 'Undefined error', data: riObject}
},
getSuccessInfo: async (riObject, requestClass, Config) => {
return riObject.data
},
},
Hook: {
RequestPromise(requestPromise, settings) {
console.log(requestPromise, settings);
}
},
RequestClient: {
name: 'AXIOS', // or FETCH
async send(obj) {
return await axios(obj);
},
async fileDownload(data, ri, requestClass, Config) {
// add file download code
return {};
},
getRequestClientObject(requestObj, requestClass, Config) {
return requestObj;
},
isNetworkError(axiosResponse, requestClass, Config) {
return false;
}
getRiObject(axiosResponse, requestClass, Config) {
return {httpStatus: 200, contentType: '', data: {}, headers: {}};
}
}
}
It describes how we will group all our requests, what parameters they will accept and in what form they will be sent. We can also change or replace the response data.
RequestSchema
// schema example
const RequestSchema = {
Auth: {
authorization: ({login, password}) => { return new RequestClass({/* ... */}); },
registration : ({email, login, password}) => { return new RequestClass({/* ... */}); },
},
News: {
getAll : () => { return new RequestClass({/* ... */}); },
getById: ({id}) => { return new RequestClass({/* ... */}); },
getOldNews: () => { return new RequestClass({/* ... */}); },
getNewNews: () => { return new RequestClass({/* ... */}); },
create:({name, desc}) => { return new RequestClass({/* ... */}); },
delete:({id}) => { return new RequestClass({/* ... */}); },
},
Tags: {
News: {
getAll : () => { return new RequestClass({/* ... */}); },
},
User: {
getAll : () => { return new RequestClass({/* ... */}); },
},
},
getTheme : () => { return new RequestClass({/* ... */}); },
};
A single request is described by a function that accepts a single object and returns RequestClass
An example of how to call it
RequestManager.Auth.authorization({login: 'admin', password: 'pass'});
RequestManager.News.getAll();
RequestManager.News.getById({id});
RequestManager.Tags.News.getAll();
RequestManager.Tags.User.getAll({}).then(console.log, console.error);
RequestManager.getTheme();
RequestSchema RequestClass
This is the class that we use to describe all our queries.
import RequestClass from "js-request-manager/src/Class/RequestClass";
request = new RequestClass({
name : '', // String - request name (need for debug or custom prepare)
type : '', // String - request type [GET|POST|PUT|DELETE ... or other custom ]
url : '', // String - request url
params : {
get : {}, // Send GET params
post: {}, // Send POST params
},
responsePrepare: (response) => { // Function
return {token: response.jwt}; // change response
},
cache : false, // Create request cache
errorMessage: '', // String or Function
// For load file
fileName: 'test.txt', // String or Function
})
Here we can change the standard behavior of the Request Manager, subscribe to events, set an alias for the url. Next, you can see detailed information on RequestSchema and Config
Config will be described in the form Config.[Type].[Sub type] = [Settings]
Config.hostSchema
Setting an alias for the url. We do this in order not to write full domain names in all requests. Example:
const hostSchema = {
auth : 'https://auth.domain.test/api',
apiV1 : 'https://domain.test/api/v1',
apiV2 : 'https://v2.domain.test/api',
};
In the future, you can use abbreviations when describing queries
RequestClass({ url: 'auth://authorize' /* ... */}); // url => https://auth.domain.test/api/authorize
RequestClass({ url: 'apiV1://users' /* ... */}); // url => https://domain.test/api/v1/users
RequestClass({ url: 'apiV2://news' /* ... */}); // url => https://v2.domain.test/api/news
Config.RequestPrepare
In this object, we can add/redefine/change/delete the data that will be in the request. RequestPrepare is an object. See the parameters (below) in Config.RequestPrepare.[Settings subtype].
Config.RequestPrepare.data
This function allows you to change/supplement / replace the request data. This works for all requests!!!
// example Config.RequestPrepare.data
function RequestPrepare_data(requestType, requestUrl, requestData) {
if(requestType === 'POST-REPLACE') { // add new types of queries if you understand what this is for.
return { test: "test"} // replace requestData
}
requestData.time = Date(); // add date in send request
if(requestData.debugInfo) {
console.log(requestData.debugInfo) // view debug info
delete requestData.debugInfo; // delete data (debugInfo) from the request
}
if(requestData.arrayInfo) {
requestData.arrayInfo = JSON.stringify(requestData.arrayInfo) // replace/conver data
}
return requestData;
}
Config.RequestPrepare.type
This function allows you to change / replace the request type. This works for all requests!!!
// example Config.RequestPrepare.type
function RequestPrepare_type(requestType, requestUrl, requestData) {
if(requestType === 'POST-REPLACE') { // add new types of queries if you understand what this is for.
return 'POST' // replace requestType
}
const allowedRequestType = {
'GET': true,
'POST': true,
}
if(!allowedRequestType[requestType]) {
console.warn('Not correct request type', requestType)
return 'GET'; // replace
}
return requestType; // return original request type
}
Config.RequestPrepare.url
This function allows you to change/replace the request url. This works for all requests!!!
// example Config.RequestPrepare.url
function RequestPrepare_url(requestType, requestUrl, requestData) {
if(requestType === 'POST-REPLACE') {
return 'https://test.domain.com/test-url' // replace url
}
return requestUrl.getUrl(); // warning requestUrl - is RequestLinkClass
}
Config.RequestPrepare.requestClientDataPrepare
This function allows you to change the object passed to axios or fetch. Add headers, settings, etc.
function RequestPrepare_requestClientDataPrepare(requestClientData, requestClass) {
let token = localStorage.getItem('user-token');
if (token) {
// add axios header
requestClientData.headers['Authorization'] = `Token ${token}`;
}
return requestClientData;
},
ps: this is a general callback
Config.ResponsePrepare
In this block, we are working with the answer (we can change it). ResponsePrepare is an object. See the parameters (below) in Config.ResponsePrepare.[Settings subtype].
Config.ResponsePrepare.isError
Here we check whether the answer is an error
function ResponsePrepare_isError(responseData){
if( !(200 <= riObject.httpStatus && riObject.httpStatus < 300) ) {
return true;
}
if(!riObject.data.success) {
return true;
}
return false;
};
Config.ResponsePrepare.getErrorInfo
If ResponsePrepare_isError is true, then we are trying to get information about the error. We give this information in the form of an object.
async function ResponsePrepare_getErrorInfo(riObject, requestClass, Config) => {
return {
code: 'error',
message: riObject.data.error || 'Unknown error',
data: riObject.data,
};
};
Config.ResponsePrepare.getSuccessInfo
If ResponsePrepare_isError is false, then we get clean data from the response. Let's look at the example of the answer:
{ "success": true, "result": { "a": 1, "b": 2} }
In this case, we want to get - {"a": 1, "b": 2}. To do this, use the following code:
async function ResponsePrepare_getSuccessInfo(riObject, requestClass, Config) => {
return riObject.data.result;
};
Config.Hook
Hook - we can subscribe to Request Managera events. Hook is an object. See the parameters (below) in Config. Hook.[Subtype of settings].
Config.Hook.RequestPromise
This event is called after the request is sent. It is applicable for loading, maintaining statistics, logging, and displaying error messages
function Hook_RequestPromise(requestPromise, settings){
requestPromise.then(
(result) => {},
(error) => {
alert(settings.errorMessage) // alert error
});
};
Config.RequestClient
This function allows you to determine through which we will send the request. Usually axios or fetch is used (you can use others as well). In the examples below, we will use axios.
In 99%, it is necessary to pass axios. This is done via Config.RequestClient.send. The rest should work correctly! To add custom tokens, headers (and so on) to axiosObj, use " Config.RequestPrepare.requestClientDataPrepare"
Overriding the rest makes sense if you are using a different client for sending or axios has released a new version (not compatible with the old one).
ps: It is worth noting axios on the front and on the back - behaves a little differently
Config.RequestClient.name
Here we are talking about which preset Config.RequestClient to use (AXIOS | FETCH). By default, the AXIOS preset will be used.
Config.RequestClient.send
Here we say that the sending will be via "axios".
import axios from 'axios';
async function RequestClient_send(obj) {
return await axios(obj);
},
Config.RequestClient.fileDownload
Here we say that we will upload files.
import fileDownload from 'js-file-download';
async fileDownload(data, ri, requestClass, Config) {
const download = async (data, ri, requestClass, Config) => {
let fileName = requestClass.getFileName();
fileDownload(ri.data, fileName, ri.contentType);
}
download(data, ri, requestClass, Config)
return {};
}
Config.RequestClient.getRequestClientObject
This setting works correctly in most cases. There is no need to redefine it without an urgent need! To add custom tokens, headers (and so on) to axiosObj, use " Config.RequestPrepare.requestClientDataPrepare" Here we convert the data from the RequestManagera request object to an axios object. We only convert it!
function getRequestClientObject(requestObj, requestClass, Config) {
const axiosObj = {
method : requestObj.type,
url : requestObj.url,
headers : {}
};
// axiosObj.responseType = 'application/json';
if(requestClass.getFileName()){
axiosObj.responseType = 'blob';
}
if(!isEmpty(requestObj.data.get)){
axiosObj.params = requestObj.data.get;
}
if(!isEmpty(requestObj.data.post)){
axiosObj.data = requestObj.data.post;
}
if(requestObj.data.post instanceof FormData){
axiosObj.data = requestObj.data.post;
axiosObj.headers['Content-Type'] = 'multipart/form-data';
}
return axiosObj;
},
Config.RequestClient.isNetworkError
In js, network errors can't get much information. If network errors occur, we handle them separately. If this is a network error , we will return a message with the error text.
This setting works correctly in most cases. There is no need to redefine it without an urgent need!
function isNetworkError(axiosResponse, requestClass, Config) {
if(/* axiosResponse.isAxiosError && */ !axiosResponse.response) {
return axiosResponse.message ? axiosResponse.message : 'Unknown network error';
}
},
Config.RequestClient.getRiObject
We pull the information from the response to riObject (internal type of Request Manager).
This setting works correctly in most cases. There is no need to redefine it without an urgent need!
function getRiObject(axiosResponse, requestClass, Config) {
const ri = {
httpStatus : -1,
contentType : '',
data : {},
headers : {}
}
const clearContentType = (contentType) => {
return contentType ? contentType.split(';')[0] : '';
}
// get status
if(axiosResponse.status) {
ri.httpStatus = axiosResponse.status;
} else if(axiosResponse.request && axiosResponse.request.status) {
ri.httpStatus = axiosResponse.request.status;
}
// get headers
if(axiosResponse.headers) {
ri.headers = axiosResponse.headers;
}
// get contentType
if( ri.headers['content-type']) {
ri.contentType = clearContentType( axiosResponse.headers['content-type'] );
}
// get data
if(axiosResponse.data){
ri.data = axiosResponse.data;
if(ri.data instanceof Blob){
ri.contentType = clearContentType( ri.data.type );
}
}
return ri;
},
- Ability to integrate into any js project (webpack, Vue, React, Next, Nuxt, Angular and ...).
- 1 sending point for all requests.
- The ability to create an sdk and reuse it in different projects.
- Ease of use (grouping by type, uniformity, response prepare).
- Query caching (optional, disabled by default).
- Working with fake data.
- Use of global error handlers (user notify)
- Logger, Statistics, Loading, ...
A lot of this requires integration with your system. The final functionality is up to you to determine.
- There is no way to make a universal solution (the variety of APIs knows no boundaries). Perhaps someone will need additional functionality. Such cases are reduced to a minimum, but they can still happen.
- Complexity. Integration with the API requires experience. You need to understand what is happening and where. This solution makes it easier, but I always want it easier...
- The solution is designed for communication via json. To work with other formats (xml, yaml,...) , you may need to use additional librari