js-request-manager

1.2.2 • Public • Published

API request Manager.

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.

Documentation in Russian

How it looks after setting up

async/await

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;
}

Promise

RequestManager.Auth.authorization({login: 'admin', password: 'pass'}).then(
  (result)  => { /* ... user code for success response */ },
  (error)   => { /* ... user code for error response or server error */ }
);

Installation

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",
    // ..
  }
}

Initialization (options):

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

RequestManager accepts the following settings:

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: {}};
    }
  }
}

RequestSchema setting information

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
})

Config setting information

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;
},

Advantages

  • 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.

Disadvantages

  • 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

Dependencies (0)

    Dev Dependencies (22)

    Package Sidebar

    Install

    npm i js-request-manager

    Weekly Downloads

    19

    Version

    1.2.2

    License

    MIT

    Unpacked Size

    83.5 kB

    Total Files

    16

    Last publish

    Collaborators

    • oploshka