Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/RasCarlito/axios-cache-adapter
Caching adapter for axios. Store request results in a configurable store to prevent unneeded network requests.
https://github.com/RasCarlito/axios-cache-adapter
Last synced: about 2 months ago
JSON representation
Caching adapter for axios. Store request results in a configurable store to prevent unneeded network requests.
- Host: GitHub
- URL: https://github.com/RasCarlito/axios-cache-adapter
- Owner: lisaogren
- License: mit
- Archived: true
- Created: 2017-09-14T08:06:20.000Z (about 7 years ago)
- Default Branch: master
- Last Pushed: 2022-02-06T17:40:59.000Z (over 2 years ago)
- Last Synced: 2024-05-19T12:17:46.231Z (4 months ago)
- Language: JavaScript
- Homepage:
- Size: 3.21 MB
- Stars: 724
- Watchers: 8
- Forks: 109
- Open Issues: 59
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# :rocket: axios-cache-adapter [![Build Status](https://travis-ci.org/RasCarlito/axios-cache-adapter.svg?branch=master)](https://travis-ci.org/RasCarlito/axios-cache-adapter) [![codecov](https://codecov.io/gh/RasCarlito/axios-cache-adapter/branch/master/graph/badge.svg)](https://codecov.io/gh/RasCarlito/axios-cache-adapter) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
> Caching adapter for axios. Store request results in a configurable store to prevent unneeded network requests.
Adapted from [superapi-cache](https://github.com/stephanebachelier/superapi-cache)
by [@stephanebachelier](https://github.com/stephanebachelier)## Install
Using npm
```sh
npm install --save axios-cache-adapter
```Or bower
```sh
bower install --save axios-cache-adapter
```Or from a CDN like unpkg.com
```html
```
## 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`.
```js
// 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)
})
```### 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.
```js
// 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)
})
```### 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.
```js
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_
### 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.
```js
import { setup } from 'axios-cache-adapterconst 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.
### 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](https://github.com/RasCarlito/axios-cache-adapter/blob/master/src/memory.js) store.
_Note: This only works client-side because `localforage` does not work in Node.js_
```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` ;)
})
```### 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](https://github.com/RasCarlito/axios-cache-adapter/blob/master/src/memory.js) store.
_Note: This only works server-side_
```js
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')
```#### 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_
```js
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')
```### 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.
```js
// 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()
```### 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`.
```js
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_
### 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`.
```js
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.
```js
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)
})
```### Use response headers to automatically set `maxAge`
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.
```js
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._
## 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
```js
// 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
}
```#### 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
```js
{
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`](https://github.com/mzabriskie/axios#creating-an-instance) 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](https://redis.io/).
Create a `RedisStore` instance. Takes `client` (`RedisClient`) and optional `hashKey` (name of hashSet to be used in redis).#### client
```js
// 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()
```#### 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.
```js
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`.
## Building
```sh
npm run build
```Webpack is used to build [umd](https://github.com/umdjs/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](https://github.com/karma-runner/karma).
To launch a single run tests using ChromeHeadless:
```sh
npm test
```To launch tests in watch mode in Chrome for easier debugging with devtools:
```sh
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](https://github.com/RasCarlito/axios-cache-adapter/blob/master/src/memory.js). 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`](https://github.com/localForage/localForage) API and `localForage` does not work in nodejs for very good reasons that are better explained in [this issue](https://github.com/localForage/localForage/issues/57).
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](https://github.com/RasCarlito)