Skip to content

Instantly share code, notes, and snippets.

@opentechnologist
Created September 29, 2025 05:36
Show Gist options
  • Save opentechnologist/f4d3d2443eeaeb16616e76621e4036d3 to your computer and use it in GitHub Desktop.
Save opentechnologist/f4d3d2443eeaeb16616e76621e4036d3 to your computer and use it in GitHub Desktop.
a simple axios request interceptor implementation to cache response data for a particular period of time to optimize multiple endpoint requests.
class ApiCache {
static CACHE_KEY_PREFIX = '__API_CACHE__';
constructor(axios) {
this.axios = axios.create();
this.axios.interceptors.request.use(this.requestInterceptor.bind(this));
}
// djb2 hash function
hash(str) {
let hash = 5381;
for (let i = 0; i < str.length; i++) {
hash = (hash << 5) + hash + str.charCodeAt(i);
}
return (hash >>> 0).toString(36);
}
buildCacheKey(url, params) {
const str = params
? Object.keys(params)
.sort()
.map((k) => `${k}=${encodeURIComponent(params[k])}`)
.join('&')
: '';
const hash = this.hash(`${url}?${str}`);
return `${ApiCache.CACHE_KEY_PREFIX}${hash}`;
}
requestInterceptor(config) {
const { url, params, cacheDuration } = config;
if (!cacheDuration) return config;
const cacheKey = this.buildCacheKey(url, params);
const cached = localStorage.getItem(cacheKey);
if (cached) {
let expired = true;
try {
const { expiration, data } = JSON.parse(cached);
const now = Date.now();
if (now < expiration) {
console.log(`CACHE HIT [${expiration - now}]`);
expired = false;
return Promise.reject({
__cachedData: true,
config,
data,
status: 200,
statusText: 'OK (CACHED)',
headers: {},
});
}
// } catch (e) {
} finally {
if (expired) {
console.log(`CACHE EXPIRED [${expiration - now}]`);
localStorage.removeItem(cacheKey);
}
}
} else {
console.log('CACHE MISS');
}
return config;
}
get(url, params = {}, config = {}) {
const cacheDuration = config.cacheDuration;
const millisecond = cacheDuration ? cacheDuration * 60 * 1000 : 0;
const expiration = cacheDuration ? Date.now() + millisecond : undefined;
const cacheKey = this.buildCacheKey(url, params);
const axiosConfig = Object.assign({}, config, { params, cacheDuration });
return this.axios
.get(url, axiosConfig)
.then((response) => {
const data = response.data;
if (cacheDuration) {
localStorage.setItem(
cacheKey,
JSON.stringify({
expiration,
data,
url,
params,
}),
);
}
return response.data;
})
.catch((error) => {
if (error.__cachedData) {
return error.data;
}
throw error;
});
}
remove(url, params = {}) {
const cacheKey = this.buildCacheKey(url, params);
localStorage.removeItem(cacheKey);
}
clearExpired() {
Object.keys(localStorage).forEach((key) => {
if (key.startsWith(ApiCache.CACHE_KEY_PREFIX)) {
const { expiration } = JSON.parse(cached);
const now = Date.now();
if (expiration < now) {
localStorage.removeItem(key);
}
}
});
}
clearAll() {
Object.keys(localStorage).forEach((key) => {
if (key.startsWith(ApiCache.CACHE_KEY_PREFIX)) {
localStorage.removeItem(key);
}
});
}
}
const api = new ApiCache(axios);
api
.get(
'https://jsonplaceholder.typicode.com/photos',
{},
{
cacheDuration: 5, // in minutes
},
)
.then((response) => console.log(response));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment