Configurable cache adapter for Axios, working in both Browser and Node.
Features:
- ✅ Supports both sync and async storage
- ✅ Supports user defined storage
- ✅ Supports cache-control headers
- ✅ Supports per endpoint caching configuration
- ✅ Supports defaults
Why this library?
- 💯 Written in typescript
- 💯 Rigorously tested
- 💯 Simple to use
The simplest way to use this library is to create an adapter and pass it to axios:
import axios from 'axios';
import { AxiosCacheRequestConfig, createCacheAdapter } from 'axios-simple-cache-adapter';
const adapter = createCacheAdapter();
// use the adapter as part of the axios request config
async function makeAPICall(): Promise<SomeInterface> {
const response = await axios.get<SomeInterface>('some/url', {
adapter,
cache: 1000, // value in MS
} as AxiosCacheRequestConfig);
return response.data;
}
// or pass it as part of the defaults passed to axios.create:
const instance = axios.create({ baseURL: 'https://myapi.com', adapter });
The createCacheAdapter
function accepts an options object with the following signature:
interface AxiosCacheOptions {
debug?: boolean;
defaultTTL?: number;
logger?: CacheLogger;
parseHeaders?: boolean;
storage?: AxiosCacheStorage | AsyncAxiosCacheStorage;
}
debug
: log debug message, defaults to falsedefaultTTL
: default TTL to use when enabling caching for a particular endpoint, defaults to undefinedlogger
: logger to use when debug=true, defaults to consoleparseHeaders
: parse cache-control headers on the response, defaults to falsestorage
: storage to use, defaults to localStorage in the browser and a simple memory based caching in node.
This library extends the AxiosRequestConfig
with an additional key called cache
:
interface AxiosCacheRequestConfig extends AxiosRequestConfig {
cache?: boolean | number;
}
You can use it on a per endpoint basis. If the value is a number, this endpoint will be cached for the particular TTL specified:
async function makeAPICall(): Promise<SomeInterface> {
const response = await axios.get<SomeInterface>('some/url', {
adapter,
cache: 1000, // value in MS
} as AxiosCacheRequestConfig);
return response.data;
}
If it is true
then caching will occur with the defaultTTL
parameter passed to createCacheAdapter
. If no value is
passed, caching will not occur:
const adapterWithDefaultTTL = createCacheAdapter({
defaultTTL: 1000, // one second
});
// here caching will occur
async function makeAPICall(): Promise<SomeInterface> {
const response = await axios.get<SomeInterface>('some/url', {
adapter: adapterWithDefaultTTL,
cache: true,
} as AxiosCacheRequestConfig);
return response.data;
}
const adapterWithoutDefaultTTL = createCacheAdapter();
// here caching will not occur
async function makeAPICall(): Promise<SomeInterface> {
const response = await axios.get<SomeInterface>('some/url', {
adapter: adapterWithoutDefaultTTL,
cache: true,
} as AxiosCacheRequestConfig);
return response.data;
}
If though the value of cache
is false
, no caching will occur for that particular endpoint, regardless of defaultTTL
and any cache-control headers:
const adapterWithDefaultTTL = createCacheAdapter({
defaultTTL: 1000, // one second
});
// no caching will occur here, even if cache-control headers are present
async function makeAPICall(): Promise<SomeInterface> {
const response = await axios.get<SomeInterface>('some/url', {
adapter: adapterWithDefaultTTL,
cache: false,
} as AxiosCacheRequestConfig);
return response.data;
}
When to use this feature?
cache-control headers are commonly used in
APIs to ensure a browser and/or network intermediaries, cache a response. As such, in a browser environment, the browser
should usually take care of caching - not the code. The main reason to use cache-control header based caching in the
browser, is for using the same cache across multiple tabs - this is possible when using localStorage or a library such
as localForage
. But if you intend to use sessionStorage
, this will be redundant.
In a nodeJS environment on the other hand, there is no browser involved and cache-control headers should be dealt with more explicitly. Here its a good idea to use these headers as a source of truth for cache TTL. Furthermore, if you use a storage backend that is shared across multiple instances of your server, e.g. a redis cache, you will be able to share cached responses.
If you pass an explicit cache value as part of the request config, this value will override whatever cache-control headers are in place:
const instance = axios.create({ baseURL: 'https://myapi.com', adapter });
const response = await instance.get('endpoint-with-cache-control', {
cache: 10000,
});
// response.headers["cache-control"] === "public, max-age=15" (15 seconds)
// caching here will be 10000, i.e. 10 seconds
This library is agnostic regarding the storage used. You are free to pass any storage - sync or async - to
the createCacheAdapter
function. You must though make sure that the storage object you are passing fulfills one of the
following interfaces:
interface StorageLikeCache {
getItem(key: string): string | null;
setItem(key: string, value: string): any;
removeItem(key: string): any;
}
interface AsyncStorageLikeCache {
getItem(key: string): Promise<string | null>;
setItem(key: string, value: string): Promise<any>;
removeItem(key: string): Promise<any>;
}
interface MapLikeCache {
get(key: string): string | null;
set(key: string, value: string): void;
delete(key: string): any;
}
interface AsyncMapLikeCache {
get(key: string): Promise<string | null>;
set(key: string, value: string): Promise<any>;
delete(key: string): Promise<any>;
}
interface CacheManagerLikeCache {
get(key: string): string | null;
set(key: string, value: string): void;
del(key: string): any;
}
interface AsyncCacheManagerLikeCache {
get(key: string): Promise<string | null>;
set(key: string, value: string): Promise<any>;
del(key: string): Promise<any>;
}
type AxiosCacheStorage =
| StorageLikeCache
| AsyncStorageLikeCache
| MapLikeCache
| AsyncMapLikeCache
| CacheManagerLikeCache
| AsyncCacheManagerLikeCache;
Thus, you can easily pass sessionStorage
, a library storage such as
localForage, a node caching library
like node-cache-manager, or even just a simple new Map<string,string>()
.
This library is open source. As such contributions of any kind welcome! Please see the contributing guide.