npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

my-zxventures-axios-cache

v1.0.4

Published

Using axios with cache (redis) for node.js

Downloads

24

Readme

@zxventures/axios-cache (icp-pkg-axios-cache)

Table of Contents

Overview (or Why)

The goal of this library was to enable Axios along with a fast cache handling and to fix a few issues with the current library:

  • Requests with status 200 but with errors node present.
  • Lack of control over internal data management, for example, serialized errors.
  • High level of complexity for use, during the development process.
  • Simplify the clusters handling.

Quick start

Requirements

  • Redis server must be installed and the service must be enabled.
  • Supports Redis >= 2.6.12
  • Node.js >= 12.22.0

Install

This package is scoped and is private by default.

Therefore, to use this package, it is necessary to include a file named .npmrc at the root of the project where this package will be used with the following content:

//registry.npmjs.org/:_authToken=PUT-THE-AUTHENTICATION-TOKEN-HERE

NOTE: The authentication token should be requested from the infrastructure team.

Using npm:

npm install @zxventures/axios-cache

Usage

In this package there are some clients, helpers, validations and others. All of them can be customized, extended or used individually.

However, the simplest way is to use the axiosCache function. This function allows us to create a new instance of axios with some extra features to work with redis cache and custom validation of responses with status 200 but with the error node present, usually in GraphQL responses.

In the same way that axios works, it can be a general instance with some specific configuration. Likewise, it can override the instance configuration when using the request function or some shortcut functions like .get, .post, etc.

By default, caching is disabled, but it can be enabled by setting some properties in the configuration.

The axiosCache function receives as an optional parameter a configuration. This configuration has 2 optional properties. One of them is axiosConfig as its name indicates is the axios configuration (in a normal way) with 2 additional properties: validateResponse and cache. Another property is redisClientConfig and it is to set the custom redis client configuration.

const axiosCache = require('@zxventures/axios-cache');

const config = {
  axiosConfig: { /* ... */ },
  redisClientConfig: { /* ... */ }
};

const axiosInstance = axiosCache(config);

The custom redis client in this package uses the ioredis library to work with redis. Therefore, the complete configuration of redis is specified in the official documentation of the ioredis library. So then, the redisClientConfig configuration has the following schema and default values:

const axiosCache = require('@zxventures/axios-cache');

const redisClientConfig = {
  /*
   *  ioredis configuration to work with a standard redis server (no cluster)
   *  - https://github.com/luin/ioredis
   *  - https://luin.github.io/ioredis/classes/Redis.html
   *
   */
  redisConfig: {
    lazyConnect: true,
    connectTimeout: 10000,
    enableOfflineQueue: false,
  },

  /*
   *  ioredis configuration to work with the Cluster approach
   *  - https://github.com/luin/ioredis#cluster
   *  - https://luin.github.io/ioredis/classes/Cluster.html
   *
   */
  clusterConfig: {
    nodes: [],
    options: { },
  },

  /*
   *  Additionals props
   *
   */

  // `maxAgeInMs` defines the default cache lifetime in milliseconds.
  // - Type: Number
  // - Default value is `Number.NaN`.
  maxAgeInMs: Number.NaN,
};

const axiosInstance = axiosCache({ redisClientConfig });

To enable the cache, you only have to set the values of the properties in the cache property correctly.

const axiosCache = require('@zxventures/axios-cache');

const axiosConfig = {
  cache: {
    // `isActive` indicates whether or not to use the cache.
    // - Type: Boolean
    // - Default value is `false`.
    isActive: true, 

    // `maxAgeInMs` defines the cache lifetime in milliseconds.
    // this value must be greater than zero in order to be valid to use the cache.
    // - Type: Number
    // - Default value is `Number.NaN`.
    maxAgeInMs: 1000 * 30, // e.g: 30 seconds

    // `methodsToCache` to define which http methods will be valid to use the cache.
    // - Type: Array of strings
    // - Default value is `['get', 'post']`.
    methodsToCache: ['get', 'post'],

    // `configPathsToCreateKey` allows to define which configuration paths/properties
    // will be used to create the cache key.
    // If the value is not valid (e.g: empty array or a null/undefined) the cache key 
    // will be created using the full config.
    // - Type: Array of strings (no deep path/property)
    // - Default value is `['method', 'url', 'params', 'data']`.
    configPathsToCreateKey: ['method', 'url', 'params', 'data'],
  },
};

const axiosInstance = axiosCache({ axiosConfig });

Note that to enable and be able to use the cache, at least the following rules must be met:

  1. The value of config.cache.isActive must be true.
  2. The value of config.cache.maxAgeInMs greater than zero.
  3. One of the array values in config.cache.methodsToCache must match the config.method specified in an axios request.

Examples

Below are some useful examples that will help you understand how this library works.

- Example #1

This is an example of a simple get request using cache.

Note how the flow is logged (console.log) with some of the most relevant values in each step. Also, when axiosCache is used, the response schema has an additional property called isCachedData.

const axiosCache = require('@zxventures/axios-cache');

// general variables
const traceTime = "Example #1 - Time taken";

// setup the configs
const axiosConfig = {
  cache: {
    isActive: true, 
    maxAgeInMs: 1000 * 60, // 1 min.
  },
};

const redisClientConfig = {
  redisConfig: {
    host: process.env.AXIOS_CACHE_HOST,
    port: process.env.AXIOS_CACHE_PORT,
  },
};

// starts a timer that can be used to compute the duration of an operation
console.time(traceTime);

// create an axios instance using the configurations
const axiosInstance = axiosCache({ axiosConfig, redisClientConfig });

// make a get request
const response = await axiosInstance.get('https://httpstat.us/200');

// log the response
const { isCachedData, status, statusText, data } = response;
console.log('response', { isCachedData, status, statusText, data });

// stop the timer and print the result
console.timeEnd(traceTime);

/*  
 * Console output:
 * ———————————————————————
 *  • [RedisClient] connect() { statusBeforeTryingToConnect: 'wait', isConnected: false }
 *  • [RedisClient] get() {
 *    cachedDataFound: false,
 *    key: '39141ac79410dd0cb7948b0d83d398c69f3247c96a3219d1a19ebe53a5fdafe6'
 *  }
 *  • [adapter] request(): axiosConfig {
 *   method: 'get',
 *   url: 'https://httpstat.us/200',
 *   data: undefined,
 *   headers: { Accept: 'application/json...' },
 *   adapter: [Function (anonymous)],
 *   validateResponse: [Function: validateResponse],
 *   cache: {
 *     isActive: true,
 *     maxAgeInMs: 60000,
 *     methodsToCache: [ 'get', 'post' ],
 *     configPathsToCreateKey: [ 'method', 'url', 'params', 'data' ]
 *   }
 *   ...
 *  }
 *  • [RedisClient] set() {
 *    ms: 60000,
 *    key: '39141ac79410dd0cb7948b0d83d398c69f3247c96a3219d1a19ebe53a5fdafe6'
 *  }
 *  • [RedisClient] disconnect() { statusBeforeTryingToDisconnect: 'ready', isConnected: true }
 * response {
 *    isCachedData: false,
 *    status: 200,
 *    statusText: 'OK',
 *    data: { code: 200, description: 'OK' }
 *  }
 * Example #1 - Time taken: 797.941ms
 * 
 */

If the above example is run again, the output looks like this:

/*  
 * Console output:
 * ———————————————————————
 *  • [RedisClient] connect() { statusBeforeTryingToConnect: 'wait', isConnected: false }
 *  • [RedisClient] get() {
 *    cachedDataFound: true,
 *    key: '39141ac79410dd0cb7948b0d83d398c69f3247c96a3219d1a19ebe53a5fdafe6'
 *  }
 *  • [RedisClient] disconnect() { statusBeforeTryingToDisconnect: 'ready', isConnected: true }
 * response {
 *    isCachedData: true,
 *    status: 200,
 *    statusText: 'OK',
 *    data: { code: 200, description: 'OK' }
 *  }
 * Example #1 - Time taken: 49.522ms
 * 
 */

Note that the request has not been made and the set method is not called (RedisClient). Also, now the value of the isCachedData property is true and the time spent is much less.

- Example #2

This is a common example of a GraphQl endpoint request. In this case the GraphQl endpoint will response with a status 200 but with errors node present.

const axiosCache = require('@zxventures/axios-cache');

// setup the configs
const axiosConfig = {
  cache: {
    isActive: true, 
    maxAgeInMs: 1000 * 60, // 1 min.
  },
};

const redisClientConfig = {
  redisConfig: {
    host: process.env.AXIOS_CACHE_HOST,
    port: process.env.AXIOS_CACHE_PORT,
  },
};

// create an axios instance using the configurations
const axiosInstance = axiosCache({ axiosConfig, redisClientConfig });

// create a request data
const sellerId = 'modeloramamxcentro';
const appToken = 'eyJ0eXAiOiJqd3QiLCJhbGciOiJ...';
const authorization = 'eyJraWQiOiJSd2tEbTZkRjBtQksrd...';

const requestData =  {
  method: 'POST',
  url: 'https://dev-api.modelorama.online/graphql/gateway',
  headers: {
    appToken,
    authorization,
  },
  data: {
    variables: {
      input: {
        sellerId,
      },
    },
    query: `
      query minimumOrderValueQuery($input: MinimumOrderValueInput) {
        minimumOrderValue(input: $input)
      }
    `,
  },
};

try {
  // make a request
  const response = await axiosInstance.request(requestData);

  // log the response
  const { isCachedData, status, statusText, data } = response;
  console.log('response', { isCachedData, status, statusText, data });
} catch (error) {
  // log the error
  const { message, code, name, innerError } = error;
  console.error('error', { message, code, name, innerError });
}

/*  
 * Console output:
 * ———————————————————————
 *  • [RedisClient] connect() { statusBeforeTryingToConnect: 'wait', isConnected: false }
 *  • [RedisClient] get() {
 *    cachedDataFound: false,
 *    key: '510bff30f5ac9d7d46104bddde88d3fe612769ceead537d56979bf637aee1a91'
 *  }
 *  • [adapter] request(): axiosConfig {
 *   method: 'post',
 *   url: 'https://dev-api.modelorama.online/graphql/gateway',
 *   data: '{"variables":{"input":{"sellerId":"modeloramamxcentro"}},"query":"..."}',
 *   headers: { 
 *     Accept: 'application/json...',
 *     'Content-Type': 'application/json',
 *     appToken: 'eyJ0eXAiOiJqd3QiLCJhbGciOiJ...',
 *     authorization: 'eyJraWQiOiJSd2tEbTZkRjBtQksrd...',
 *   },
 *   adapter: [Function (anonymous)],
 *   validateResponse: [Function: validateResponse],
 *   cache: {
 *     isActive: true,
 *     maxAgeInMs: 60000,
 *     methodsToCache: [ 'get', 'post' ],
 *     configPathsToCreateKey: [ 'method', 'url', 'params', 'data' ]
 *   }
 *   ...
 *  }
 *  • [RedisClient] disconnect() { statusBeforeTryingToDisconnect: 'ready', isConnected: true }
 * error {
 *   message: 'Error trying to get the minimum order value',
 *   code: 'IOP-CHK-3001',
 *   name: 'AxiosCacheError',
 *   innerError: [AxiosError: Error trying to get the minimum order value] {
 *     ...
 *   },
 *  }
 * 
 */

Note that the request fails with an error of type AxiosCacheError which inherits from CustomBaseError (base error type from @zxventures/utils library). This is the error type used by this library to wrap the original error (AxiosError) and has 3 main properties: message, code and innerError.

Thanks to the extra property called validateResponse the new flow (custom adapter) checks the response object with status 200 but with internal error nodes. Thus, it converts the response into an error using the information obtained from the internal error nodes.

To customize the validateResponse function it is only necessary to override the original custom function This function receives a single argument called response and no results are expected from it but you can alter the response object or throw an error.

It is important to note that at this point in the axios flow there are 2 things to keep in mind:

  • the 'response.data' must be a String (serialized data).
  • transform responses haven't been executed yet.
const axiosCache = require('@zxventures/axios-cache');

// setup the axios config
const axiosConfig = {
  // `cache` defines how to work with axios-cache
  // - Type: Object
  cache: { /* ... */ },

  // `validateResponse` defines a function that receives a single argument called `response`
  // and no results are expected from it.
  // - Type: Function (void)
  // - Default value is custom validation to check the response object with 
  //   status 200 but with internal error nodes.
  validateResponse: (response) => { /* some custom validation */ }
};

// create an axios instance using the configuration
const axiosInstance = axiosCache({ axiosConfig });

// ...

- Example #3

This is an example where a base cache configuration is used in a global instance of axios. Then, when a request is made, the cache configuration is overwritten. It is useful when a specific use case requires some more specific configuration, for example: changing the lifetime of a cache or enable/disable the cache.

const axiosCache = require('@zxventures/axios-cache');

// setup the configs
const axiosConfig = {
  cache: {
    isActive: true, 
    maxAgeInMs: 1000 * 30, // 30 sec
  },
};

const redisClientConfig = { /* ... */ };

/*
 * `Client` with two particular methods (specific use cases)
 *  - getProductList(): change the lifetime of a cache
 *  - getAddressById(id): disable the cache
 *
 */
class Client {
  constructor() {
    this.axiosInstance = axiosCache({ axiosConfig, redisClientConfig });
  }

  async getProductList() {
    const config = {
      cache: { 
        // change the value of `maxAgeInMs` to 5min instead of 30sec.
        maxAgeInMs: (1000 * 60) * 5 
      },
      method: 'POST',
      headers: { /* appToken, authorization and others */ }, 
      data: { /* some request data */ },
    };

    const response = await this.axiosInstance.request(config);
    return response;
  };

  async getAddressById(id) {
    const config = {
      cache: { 
        // disable cache
        isActive: false,
      },
      method: 'POST',
      headers: { /* appToken, authorization and others */ }, 
      data: { /* some request data */ },
    };

    const response = await this.axiosInstance.request(config);
    return response;
  };

  // other methods using the standard instance of `axiosInstance`.
  // ...
}

module.exports = Client;

- Example #4

This is an example using the redis cluster. To work with the redis cluster it is only necessary to use the clusterConfig property instead of the redisConfig property and follow the instructions/guidelines of the official documentation of the ioredis library to work with cluster.

const axiosCache = require('@zxventures/axios-cache');

// setup the configs
const axiosConfig = {
  cache: {
    isActive: true, 
    maxAgeInMs: 1000 * 60, // 1 min.
  },
};

const redisClientConfig = {
  // - https://github.com/luin/ioredis#cluster
  clusterConfig: {
    nodes: [{
      port: 6380,
      host: "127.0.0.1",
    },
    {
      port: 6381,
      host: "127.0.0.1",
    }],
    options: { /* ... */ },
  },
};

// create an axios instance using the configurations
const axiosInstance = axiosCache({ axiosConfig, redisClientConfig });

// Now the `axiosInstance` is working with redis cluster.
// ...

Run unit test and code coverage

ADVICE: It is recommended to review and run the unit tests to better understand each of the utilities provided by this package.

Run

npm test

To run code coverage use

npm run coverage

Package Dependencies

In this section, include the packages used as dependencies to build this package.

Some examples can be:

Contributors