These libraries provide caching solutions for Node.js and frontend build environments, ranging from simple key-value stores to HTTP request interceptors. lru-cache, node-cache, and memory-cache offer direct in-memory storage with varying features for expiration and limits. cache-manager acts as an abstraction layer to swap storage backends without changing code. cacheable-request and axios-cache-adapter focus on caching HTTP responses transparently, reducing network load for API calls.
When building server-side rendered apps or Node.js backends, caching reduces load times and server stress. The packages listed here fall into two groups: raw storage engines (lru-cache, node-cache, memory-cache), abstraction layers (cache-manager), and HTTP-specific tools (cacheable-request, axios-cache-adapter). Let's break down how they handle data, expiration, and integration.
The core job of any cache is to save and fetch values. The API style varies from synchronous to asynchronous, and from simple objects to specialized classes.
lru-cache uses a class-based approach with strict memory limits.
import { LRUCache } from 'lru-cache';
const cache = new LRUCache({ max: 500 });
cache.set('key', 'value');
const val = cache.get('key');
node-cache offers a simple synchronous API with event support.
const NodeCache = require('node-cache');
const cache = new NodeCache();
cache.set('key', 'value');
const val = cache.get('key');
memory-cache provides a very basic static interface.
const cache = require('memory-cache');
cache.put('key', 'value');
const val = cache.get('key');
cache-manager uses an asynchronous promise-based API.
import { caching } from 'cache-manager';
const cache = await caching('memory', { ttl: 5000 });
await cache.set('key', 'value');
const val = await cache.get('key');
cacheable-request wraps HTTP methods directly.
const CacheableRequest = require('cacheable-request');
const cr = new CacheableRequest(http.request);
const req = cr.get('http://example.com');
axios-cache-adapter integrates into the axios adapter chain.
import { setupCache } from 'axios-cache-adapter';
const cache = setupCache({ maxAge: 15 * 60 * 1000 });
const api = axios.create({ adapter: cache.adapter });
Controlling how long data lives is critical to prevent stale data or memory leaks. Some packages use Time-To-Live (TTL), while others use count limits.
lru-cache focuses on item count and optional TTL.
// Limits to 100 items, auto-deletes old ones
const cache = new LRUCache({ max: 100, ttl: 1000 * 60 });
node-cache allows global or per-key TTL.
// Global 60s TTL
const cache = new NodeCache({ stdTTL: 60 });
// Per-key override
cache.set('key', 'value', 120);
memory-cache requires TTL on every put operation.
// Must specify milliseconds every time
cache.put('key', 'value', 5000);
cache-manager defines TTL during setup or call.
// Setup TTL
const cache = await caching('memory', { ttl: 5000 });
// Or per set
await cache.set('key', 'value', 3000);
cacheable-request respects HTTP headers primarily.
// Uses Cache-Control headers from server
const req = cr.get('http://example.com');
axios-cache-adapter sets maxAge in configuration.
// Cache for 15 minutes
const cache = setupCache({ maxAge: 15 * 60 * 1000 });
Some tools cache any JavaScript value, while others specialize in network responses. Mixing these up can lead to architecture issues.
lru-cache, node-cache, and memory-cache store any data type. You must manually manage when to invalidate data after an API call.
// Manual HTTP caching logic
const data = await fetchAPI();
cache.set('api-result', data);
cache-manager stores any data but often pairs with HTTP logic.
// Wrap function to cache result
const user = await cache.wrap('user-1', fetchUser);
cacheable-request and axios-cache-adapter handle the network layer directly. They skip the request if a valid cache exists.
// cacheable-request: Automatic based on URL
const req = cr.get('http://api.com/data');
// axios-cache-adapter: Automatic based on config
const response = await api.get('/data');
Choosing a library involves checking if it is still safe to use. Some packages are legacy or have newer successors.
memory-cache is simple but lacks recent updates. It does not support events or advanced TTL features found in node-cache. Use it only for quick scripts.
// Limited feature set
const val = cache.get('key');
axios-cache-adapter has seen less activity compared to axios-cache-interceptor. For new projects, evaluate if the interceptor fits better.
// Legacy adapter pattern
axios.create({ adapter: cache.adapter });
lru-cache, node-cache, and cache-manager are actively maintained. They receive security patches and feature updates regularly.
// Safe for production
const cache = new LRUCache({ max: 100 });
cacheable-request is stable and follows HTTP standards closely.
// Standard compliant
const cr = new CacheableRequest(http.request);
| Package | Type | Async | TTL Support | Max Items | HTTP Aware |
|---|---|---|---|---|---|
lru-cache | Storage | No | Yes | Yes | No |
node-cache | Storage | No | Yes | No | No |
memory-cache | Storage | No | Yes | No | No |
cache-manager | Abstraction | Yes | Yes | Depends | No |
cacheable-request | HTTP | Yes | Header-based | No | Yes |
axios-cache-adapter | HTTP | Yes | Config-based | No | Yes |
For general server-side caching where you control the data, node-cache offers the best balance of features and stability. If you need strict memory limits to prevent crashes, lru-cache is the industry standard.
For HTTP requests, cacheable-request is ideal for native modules, while axios-cache-adapter works for axios users — though check if axios-cache-interceptor suits your needs better. Use cache-manager if you plan to switch to Redis later. Avoid memory-cache for critical production systems due to limited features.
Choose this if you are already using axios and need a quick way to cache HTTP responses without changing your request logic. It bridges axios with storage engines like cache-manager. Note that newer projects might prefer axios-cache-interceptor for active maintenance.
Choose this if you need a unified interface that allows switching between memory, Redis, or file stores later. It is ideal for applications that might scale from single-server memory to distributed caching without rewriting core logic.
Choose this if you are using native http or got modules and want transparent HTTP caching based on standard headers. It works well for low-level network tools where you need strict adherence to HTTP caching rules.
Choose this if you need a fast, dependency-free Least Recently Used cache with precise control over memory limits. It is the standard choice for internal data structures where you must prevent memory leaks.
Choose this only for simple prototypes or scripts where you need basic key-value storage with minimal setup. For production systems, prefer node-cache for better event support and maintenance.
Choose this if you need a robust in-memory cache with event emitters, flexible TTL per key, and a stable API. It is the go-to for general-purpose server-side caching without external dependencies.
Caching adapter for axios. Store request results in a configurable store to prevent unneeded network requests.
Adapted from superapi-cache by @stephanebachelier
Using npm
npm install --save axios-cache-adapter
Or bower
bower install --save axios-cache-adapter
Or from a CDN like unpkg.com
<script type="text/javascript" src="https://unpkg.com/axios-cache-adapter"></script>
Important note: Only GET request results are cached by default. Executing a request using any method listed in exclude.methods will invalidate the cache for the given URL.
You can instantiate the axios-cache-adapter on its own using the setupCache() method and then attach the adapter manually to an instance of axios.
// Import dependencies
import axios from 'axios'
import { setupCache } from 'axios-cache-adapter'
// Create `axios-cache-adapter` instance
const cache = setupCache({
maxAge: 15 * 60 * 1000
})
// Create `axios` instance passing the newly created `cache.adapter`
const api = axios.create({
adapter: cache.adapter
})
// Send a GET request to some REST api
api({
url: 'http://some-rest.api/url',
method: 'get'
}).then(async (response) => {
// Do something fantastic with response.data \o/
console.log('Request response:', response)
// Interacting with the store, see `localForage` API.
const length = await cache.store.length()
console.log('Cache store length:', length)
})
You can use the setup() method to get an instance of axios pre-configured with the axios-cache-adapter. This will remove axios as a direct dependency in your code.
// Import dependencies
import { setup } from 'axios-cache-adapter'
// Create `axios` instance with pre-configured `axios-cache-adapter` attached to it
const api = setup({
// `axios` options
baseURL: 'http://some-rest.api',
// `axios-cache-adapter` options
cache: {
maxAge: 15 * 60 * 1000
}
})
// Send a GET request to some REST api
api.get('/url').then(async (response) => {
// Do something awesome with response.data \o/
console.log('Request response:', response)
// Interacting with the store, see `localForage` API.
const length = await api.cache.length()
console.log('Cache store length:', length)
})
After setting up axios-cache-adapter with a specific cache configuration you can override parts of that configuration on individual requests.
import { setup } from 'axios-cache-adapter'
const api = setup({
baseURL: 'https://httpbin.org',
cache: {
maxAge: 15 * 60 * 1000
}
})
// Use global instance config
api.get('/get').then((response) => {
// Do something awesome with response
})
// Override `maxAge` and cache URLs with query parameters
api.get('/get?with=query', {
cache: {
maxAge: 2 * 60 * 1000, // 2 min instead of 15 min
exclude: { query: false }
}
})
.then((response) => {
// Do something beautiful ;)
})
Note: Not all instance options can be overridden per request, see the API documentation at the end of this readme
You can allow axios-cache-adapter to cache the results of a request using (almost) any HTTP method by modifying the exclude.methods list.
import { setup } from 'axios-cache-adapter
const api = setup({
baseURL: 'https://httpbin.org',
cache: {
exclude: {
// Only exclude PUT, PATCH and DELETE methods from cache
methods: ['put', 'patch', 'delete']
}
}
})
api.post('/post').then((response) => {
// POST request has been cached \o/
})
Note: the request method is not used in the cache store key by default, therefore with the above setup, making a GET or POST request will respond with the same cache.
You can give a localforage instance to axios-cache-adapter which will be used to store cache data instead of the default in memory store.
Note: This only works client-side because localforage does not work in Node.js
import localforage from 'localforage'
import memoryDriver from 'localforage-memoryStorageDriver'
import { setup } from 'axios-cache-adapter'
// `async` wrapper to configure `localforage` and instantiate `axios` with `axios-cache-adapter`
async function configure () {
// Register the custom `memoryDriver` to `localforage`
await localforage.defineDriver(memoryDriver)
// Create `localforage` instance
const forageStore = localforage.createInstance({
// List of drivers used
driver: [
localforage.INDEXEDDB,
localforage.LOCALSTORAGE,
memoryDriver._driver
],
// Prefix all storage keys to prevent conflicts
name: 'my-cache'
})
// Create `axios` instance with pre-configured `axios-cache-adapter` using a `localforage` store
return setup({
// `axios` options
baseURL: 'http://some-rest.api',
// `axios-cache-adapter` options
cache: {
maxAge: 15 * 60 * 1000,
store: forageStore // Pass `localforage` store to `axios-cache-adapter`
}
})
}
configure().then(async (api) => {
const response = await api.get('/url')
// Display something beautiful with `response.data` ;)
})
You can give a RedisStore instance to axios-cache-adapter which will be used to store cache data instead of the default in memory store.
Note: This only works server-side
const { setup, RedisStore } = require('axios-cache-adapter')
const redis = require('redis')
const client = redis.createClient({
url: 'REDIS_URL',
})
const store = new RedisStore(client)
const api = setup({
// `axios` options
baseURL: 'http://some-rest.api',
// `axios-cache-adapter` options
cache: {
maxAge: 15 * 60 * 1000,
store // Pass `RedisStore` store to `axios-cache-adapter`
}
})
const response = await api.get('/url')
You can give a RedisDefaultStore instance to axios-cache-adapter which will be used to store cache data in Redis using the default commands instead of hash commands.
Note: This only works server-side
const { setup, RedisDefaultStore } = require('axios-cache-adapter')
const redis = require('redis')
const client = redis.createClient({
url: 'REDIS_URL',
})
const store = new RedisDefaultStore(client, {
prefix: 'namespace_as_prefix' // optional
})
const api = setup({
// `axios` options
baseURL: 'http://some-rest.api',
// `axios-cache-adapter` options
cache: {
maxAge: 15 * 60 * 1000,
store // Pass `RedisDefaultStore` store to `axios-cache-adapter`
}
})
const response = await api.get('/url')
When a response is served from cache a custom response.request object is created with a fromCache boolean.
// Import dependencies
import assert from 'assert'
import { setup } from 'axios-cache-adapter'
// Create `axios` instance with pre-configured `axios-cache-adapter`
const api = setup({
cache: {
maxAge: 15 * 60 * 1000
}
})
// Wrap code in an `async` function
async function exec () {
// First request will be served from network
const response = await api.get('http://some-rest.api/url')
// `response.request` will contain the origin `axios` request object
assert.ok(response.request.fromCache !== true)
// Second request to same endpoint will be served from cache
const anotherResponse = await api.get('http://some-rest.api/url')
// `response.request` will contain `fromCache` boolean
assert.ok(anotherResponse.request.fromCache === true)
}
// Execute our `async` wrapper
exec()
You can tell axios-cache-adapter to read stale cache data when a network error occurs using the readOnError option.
readOnError can either be a Boolean telling cache adapter to attempt reading stale cache when any network error happens or a Function which receives the error and request objects and then returns a Boolean.
By default axios-cache-adapter clears stale cache data automatically, this would conflict with activating the readOnError option, so the clearOnStale option should be set to false.
import { setup } from 'axios-cache-adapter'
const api = setup({
cache: {
// Attempt reading stale cache data when response status is either 4xx or 5xx
readOnError: (error, request) => {
return error.response.status >= 400 && error.response.status < 600
},
// Deactivate `clearOnStale` option so that we can actually read stale cache data
clearOnStale: false
}
})
// Make a first successful request which will store the response in cache
api.get('https://httpbin.org/get').then(response => {
// Response will not come from cache
assert.ok(response.request.fromCache !== true)
})
// Let's say that the stored data has become stale (default 15min max age has passed)
// and we make the same request but it results in an internal server error (status=500)
api.get('https://httpbin.org/get').then(response => {
// Response is served from cache
assert.ok(response.request.fromCache === true)
// We can check that it actually served stale cache data
assert.ok(response.request.stale === true)
}).catch(err => {
// Will not execute this because stale cache data was returned
// If the attempt at reading stale cache fails, the network error will be thrown and this method executed
})
Note: Passing a function to readOnError is a smarter thing to do as you get to choose when a stale cache read should be attempted instead of doing it on all kind of errors
Using the default invalidation method, a cache entry will be invalidated if a request is made using one of the methods listed in exclude.methods.
async function defaultInvalidate (config, request) {
const method = request.method.toLowerCase()
if (config.exclude.methods.includes(method)) {
await config.store.removeItem(config.uuid)
}
}
You can customize how axios-cache-adapter invalidates stored cache entries by providing a custom invalidate function.
import { setup } from 'axios-cache-adapter'
// Create cached axios instance with custom invalidate method
const api = setup({
cache: {
// Invalidate only when a specific option is passed through config
invalidate: async (config, request) => {
if (request.clearCacheEntry) {
await config.store.removeItem(config.uuid)
}
}
}
})
// Make a request that will get stored into cache
api.get('https://httpbin.org/get').then(response => {
assert.ok(response.request.fromCache !== true)
})
// Wait some time
// Make another request to same end point but force cache invalidation
api.get('https://httpbin.org/get', { clearCacheEntry: true }).then(response => {
// Response should not come from cache
assert.ok(response.request.fromCache !== true)
})
maxAgeWhen you set the readHeaders option to true, the adapter will try to read cache-control or expires headers to automatically set the maxAge option for the given request.
import assert from 'assert'
import { setup } from 'axios-cache-adapter'
const api = setup({
cache: {
// Tell adapter to attempt using response headers
readHeaders: true,
// For this example to work we disable query exclusion
exclude: { query: false }
}
})
// Make a request which will respond with header `cache-control: max-age=60`
api.get('https://httpbin.org/cache/60').then(response => {
// Cached `response` will expire one minute later
})
// Make a request which responds with header `cache-control: no-cache`
api.get('https://httpbin.org/response-headers?cache-control=no-cache').then(response => {
// Response will not come from cache
assert.ok(response.request.fromCache !== true)
// Check that query was excluded from cache
assert.ok(response.request.excludedFromCache === true)
})
Note: For the cache-control header, only the max-age, no-cache and no-store values are interpreted.
Create a cache adapter instance. Takes an options object to configure how the cached requests will be handled,
where they will be stored, etc.
// Options passed to `setupCache()`.
{
// {Number} Maximum time for storing each request in milliseconds,
// defaults to 15 minutes when using `setup()`.
maxAge: 0,
// {Number} Maximum number of cached request (last in, first out queue system),
// defaults to `false` for no limit. *Cannot be overridden per request*
limit: false,
// {Object} An instance of localforage, defaults to a custom in memory store.
// *Cannot be overridden per request*
store: new MemoryStore(),
// {String|Function} Generate a unique cache key for the request.
// Will use request url and serialized params by default.
key: req => req.url + serializeQuery(req.params),
// {Function} Invalidate stored cache. By default will remove cache when
// making a request with method not `GET`, `POST`, `PUT`, `PATCH` or `DELETE` query.
invalidate: async (cfg, req) => {
const method = req.method.toLowerCase()
if (method !== 'get') {
await cfg.store.removeItem(cfg.uuid)
}
},
// {Object} Define which kind of requests should be excluded from cache.
exclude: {
// {Array} List of regular expressions to match against request URLs.
paths: [],
// {Boolean} Exclude requests with query parameters.
query: true,
// {Function} Method which returns a `Boolean` to determine if request
// should be excluded from cache.
filter: null,
// {Array} HTTP methods which will be excluded from cache.
// Defaults to `['post', 'patch', 'put', 'delete']`
// Any methods listed will also trigger cache invalidation while using the default `config.invalidate` method.
//
// Note: the HEAD method is always excluded (hard coded).
// the OPTIONS method is ignored by this library as it is automatically handled by browsers/clients to resolve cross-site request permissions
methods: ['post', 'patch', 'put', 'delete']
},
// {Boolean} Clear cached item when it is stale.
clearOnStale: true,
// {Boolean} Clear all cache when a cache write error occurs
// (prevents size quota problems in `localStorage`).
clearOnError: true,
// {Function|Boolean} Determine if stale cache should be read when a network error occurs.
readOnError: false,
// {Boolean} Determine if response headers should be read to set `maxAge` automatically.
// Will try to parse `cache-control` or `expires` headers.
readHeaders: false,
// {Boolean} Ignore cache, will force to interpret cache reads as a `cache-miss`.
// Useful to bypass cache for a given request.
ignoreCache: false,
// {Function|Boolean} Print out debug log to console.
debug: false
}
setupCache() returns an object containing the configured adapter, the cache store and the config that is applied to this instance.
Create an axios instance pre-configured with the cache adapter. Takes an options object to configure the cache and
axios at the same time.
{
cache: {
// Options passed to the `setupCache()` method
}
// Options passed to `axios.create()` method
}
All the other parameters will be passed directly to the axios.create method.
setup() returns an instance of axios pre-configured with the cache adapter.
The cache store is conveniently attached to the axios instance as instance.cache for easy access.
RedisStore allow you to cache requests on server using redis.
Create a RedisStore instance. Takes client (RedisClient) and optional hashKey (name of hashSet to be used in redis).
// Using redis client https://github.com/NodeRedis/node_redis
// We have tested it with node_redis v.2.8.0 but it's supposed to work smoothly with the comming releases.
const redis = require("redis")
const client = redis.createClient()
new RedisStore() returns an instance of RedisStore to be passed to setupCache() as store in config object.
Using the same object definition as the setup method you can override cache options for individual requests.
api.get('https://httpbin.org/get', {
cache: {
// Options override
}
})
All options except limit and store can be overridden per request.
Also the following keys are used internally and therefore should not be set in the options: adapter, uuid, acceptStale.
npm run build
Webpack is used to build umd versions of the library that are placed in the dist folder.
cache.jscache.min.jscache.node.jscache.node.min.jsA different version of axios-cache-adapter is generated for node and the browser due to how Webpack 4 uses a target to change how the UMD wrapper is generated using global or window. If you are using the library in node or in your front-end code while using a module bundler (Webpack, rollup, etc) the correct version will be picked up automatically thanks to the "main" and "browser" fields in the package.json.
axios-cache-adapter is developped in ES6+ and uses async/await syntax. It is transpiled to ES5 using babel with preset-env.
Tests are executed using karma.
To launch a single run tests using ChromeHeadless:
npm test
To launch tests in watch mode in Chrome for easier debugging with devtools:
npm run watch
axios-cache-adapter was designed to run in the browser. It does work in nodejs using the in memory store. But storing data in memory is not the greatests idea ever.
You can give a store to override the in memory store but it has to comply with the localForage API and localForage does not work in nodejs for very good reasons that are better explained in this issue.
The better choice if you want to use axios-cache-adapter server-side is to use a redis server with a RedisStore instance as explained above in the API section.
MIT © Carl Ogren