How to Build an In-Memory Cache Axios Adapter
This article demonstrates how to create a custom Axios adapter that intercepts outgoing HTTP requests to serve cached responses from memory, reducing redundant network calls and improving application performance. You will learn the mechanics of Axios adapters, how to construct a cache key, manage in-memory storage with Time-To-Live (TTL) expiration, and bind the custom adapter to an Axios instance.
Understanding Axios Adapters
Axios delegates the actual dispatching of requests to adapters. By
default, it uses the xhr (browser) or http
(Node.js) adapter. By creating a wrapper around the default adapter, you
can inspect the request configuration before it hits the network, return
a cached response if available, or execute the network call and store
the result for future use.
Step 1: Create the Cache Store
A basic in-memory cache can be implemented using a JavaScript
Map. To prevent stale data, entries should store the
response data along with a timestamp to support TTL-based
expiration.
class MemoryCache {
constructor(defaultTTL = 60000) { // Default TTL: 60 seconds
this.cache = new Map();
this.defaultTTL = defaultTTL;
}
generateKey(config) {
const { method, url, params, data } = config;
return `${method?.toUpperCase()}:${url}:${JSON.stringify(params)}:${JSON.stringify(data)}`;
}
get(key) {
const entry = this.cache.get(key);
if (!entry) return null;
const isExpired = Date.now() > entry.expiry;
if (isExpired) {
this.cache.delete(key);
return null;
}
return entry.response;
}
set(key, response, ttl = this.defaultTTL) {
this.cache.set(key, {
response,
expiry: Date.now() + ttl,
});
}
clear() {
this.cache.clear();
}
}Step 2: Implement the Custom Adapter
An Axios adapter is a function that receives the request
config object and returns a Promise resolving
to an Axios response structure.
import axios from 'axios';
export function createCacheAdapter(options = {}) {
const cache = new MemoryCache(options.ttl);
// Retrieve the default adapter for the current environment
const defaultAdapter = axios.getAdapter(axios.defaults.adapter);
return async function cacheAdapter(config) {
// Only cache read operations (GET, HEAD, OPTIONS) by default
const isCacheable = (config.method || 'get').toLowerCase() === 'get';
if (!isCacheable || config.cache === false) {
return defaultAdapter(config);
}
const cacheKey = cache.generateKey(config);
const cachedResponse = cache.get(cacheKey);
if (cachedResponse) {
return {
...cachedResponse,
config,
request: { fromCache: true }
};
}
// Cache miss: execute network request via default adapter
const response = await defaultAdapter(config);
// Only cache successful 2xx responses
if (response.status >= 200 && response.status < 300) {
cache.set(cacheKey, {
data: response.data,
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
}
return response;
};
}Step 3: Apply the Adapter to Axios
Instantiate an Axios client and pass the custom adapter through the configuration object.
// Initialize the custom adapter with a 30-second TTL
const cacheAdapter = createCacheAdapter({ ttl: 30000 });
// Create an Axios instance
const apiClient = axios.create({
baseURL: 'https://api.example.com',
adapter: cacheAdapter,
});
// Usage
async function fetchData() {
// First call: Triggers network request and caches the result
const response1 = await apiClient.get('/users');
console.log('From network:', response1.data);
// Second call: Returned immediately from in-memory cache
const response2 = await apiClient.get('/users');
console.log('From cache:', response2.data);
// Bypass cache explicitly when needed
const response3 = await apiClient.get('/users', { cache: false });
console.log('Bypassed cache:', response3.data);
}Key Considerations
- Memory Management: For high-throughput
environments, replace
Mapwith an LRU (Least Recently Used) cache to avoid unbounded memory growth. - Mutating Requests: Automatically invalidate cache
entries when non-safe HTTP methods (
POST,PUT,DELETE,PATCH) are executed against related endpoints. - Concurrency: To prevent duplicate network requests when identical calls occur simultaneously, track in-flight promises inside the adapter and resolve them concurrently.