:rocket: axios-cache-adapter
Caching adapter for axios. Store request results in a configurable store to prevent unneeded network requests.
Adapted from superapi-cache
by @stephanebachelier
Install
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>
Usage
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.
Instantiate adapter on its own
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 axios from 'axios'
import { setupCache } from 'axios-cache-adapter'
const cache = setupCache({
maxAge: 15 * 60 * 1000
})
const api = axios.create({
adapter: cache.adapter
})
api({
url: 'http://some-rest.api/url',
method: 'get'
}).then(async (response) => {
console.log('Request response:', response)
const length = await cache.store.length()
console.log('Cache store length:', length)
})
Instantiate axios with bound adapter
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 { setup } from 'axios-cache-adapter'
const api = setup({
baseURL: 'http://some-rest.api',
cache: {
maxAge: 15 * 60 * 1000
}
})
api.get('/url').then(async (response) => {
console.log('Request response:', response)
const length = await api.cache.length()
console.log('Cache store length:', length)
})
Override instance config with per request options
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
}
})
api.get('/get').then((response) => {
})
api.get('/get?with=query', {
cache: {
maxAge: 2 * 60 * 1000,
exclude: { query: false }
}
})
.then((response) => {
})
Note: Not all instance options can be overridden per request, see the API documentation at the end of this readme
Cache POST request results
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:
cache: {
exclude: {
methods: ['put', 'patch', 'delete']
}
}
})
api.post('/post').then((response) => {
})
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.
Use localforage as cache store
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 function configure () {
await localforage.defineDriver(memoryDriver)
const forageStore = localforage.createInstance({
driver: [
localforage.INDEXEDDB,
localforage.LOCALSTORAGE,
memoryDriver._driver
],
name: 'my-cache'
})
return setup({
baseURL: 'http://some-rest.api',
cache: {
maxAge: 15 * 60 * 1000,
store: forageStore
}
})
}
configure().then(async (api) => {
const response = await api.get('/url')
})
Use redis as cache store
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({
baseURL: 'http://some-rest.api',
cache: {
maxAge: 15 * 60 * 1000,
store
}
})
const response = await api.get('/url')
Use Redis Default Store as Cache Store
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'
})
const api = setup({
baseURL: 'http://some-rest.api',
cache: {
maxAge: 15 * 60 * 1000,
store
}
})
const response = await api.get('/url')
Check if response is served from network or from cache
When a response is served from cache a custom response.request
object is created with a fromCache
boolean.
import assert from 'assert'
import { setup } from 'axios-cache-adapter'
const api = setup({
cache: {
maxAge: 15 * 60 * 1000
}
})
async function exec () {
const response = await api.get('http://some-rest.api/url')
assert.ok(response.request.fromCache !== true)
const anotherResponse = await api.get('http://some-rest.api/url')
assert.ok(anotherResponse.request.fromCache === true)
}
exec()
Read stale cache data on network error
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: {
readOnError: (error, request) => {
return error.response.status >= 400 && error.response.status < 600
},
clearOnStale: false
}
})
api.get('https://httpbin.org/get').then(response => {
assert.ok(response.request.fromCache !== true)
})
api.get('https://httpbin.org/get').then(response => {
assert.ok(response.request.fromCache === true)
assert.ok(response.request.stale === true)
}).catch(err => {
})
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
Invalidate cache entries
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'
const api = setup({
cache: {
invalidate: async (config, request) => {
if (request.clearCacheEntry) {
await config.store.removeItem(config.uuid)
}
}
}
})
api.get('https://httpbin.org/get').then(response => {
assert.ok(response.request.fromCache !== true)
})
api.get('https://httpbin.org/get', { clearCacheEntry: true }).then(response => {
assert.ok(response.request.fromCache !== true)
})
When 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: {
readHeaders: true,
exclude: { query: false }
}
})
api.get('https://httpbin.org/cache/60').then(response => {
})
api.get('https://httpbin.org/response-headers?cache-control=no-cache').then(response => {
assert.ok(response.request.fromCache !== true)
assert.ok(response.request.excludedFromCache === true)
})
Note: For the cache-control
header, only the max-age
, no-cache
and no-store
values are interpreted.
API
setupCache(options)
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
{
maxAge: 0,
limit: false,
store: new MemoryStore(),
key: req => req.url + serializeQuery(req.params),
invalidate: async (cfg, req) => {
const method = req.method.toLowerCase()
if (method !== 'get') {
await cfg.store.removeItem(cfg.uuid)
}
},
exclude: {
paths: [],
query: true,
filter: null,
methods: ['post', 'patch', 'put', 'delete']
},
clearOnStale: true,
clearOnError: true,
readOnError: false,
readHeaders: false,
ignoreCache: false,
debug: false
}
Returns
setupCache()
returns an object containing the configured adapter
, the cache store
and the config
that is applied to this instance.
setup(options)
Create an axios
instance pre-configured with the cache adapter. Takes an options
object to configure the cache and
axios at the same time.
Options
{
cache: {
}
}
All the other parameters will be passed directly to the axios.create
method.
Returns
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(client, [, hashKey])
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).
client
const redis = require("redis")
const client = redis.createClient()
Returns
new RedisStore()
returns an instance of RedisStore
to be passed to setupCache() as store
in config object.
Per request options
Using the same object definition as the setup
method you can override cache options for individual requests.
api.get('https://httpbin.org/get', {
cache: {
}
})
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
.
Building
npm run build
Webpack is used to build umd versions of the library that are placed in the dist
folder.
cache.js
cache.min.js
cache.node.js
cache.node.min.js
A 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
.
Testing
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
Browser vs Node.js
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.
License
MIT © Carl Ogren