# axios-extensions: cache, throttle and retry as Axios adapters

> axios-extensions is a set of three composable Axios adapter enhancers, for response caching, for collapsing repeated GET requests inside a time window, and for retrying transient failures. It does not replace Axios, it wraps the adapter, and it is typed by augmenting the Axios request config with four extra options.

**kuitos/axios-extensions** — 🍱 axios extensions lib, including throttle, cache, retry features etc...

- Repository: https://github.com/kuitos/axios-extensions
- Stars: 837 · Forks: 50
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/kuitos-axios-extensions

## Enhancers, not a replacement

axios-extensions gives you production-friendly request behavior without replacing Axios itself, and the whole library is three adapter enhancers.

The cacheAdapterEnhancer does request-level response caching, and the use case is avoiding duplicate reads for the same resource. The throttleAdapterEnhancer dedupes and throttles GET requests inside a time window, to prevent burst requests during rapid user interface interactions. The retryAdapterEnhancer retries failed requests with global or per-request overrides, to improve resilience on flaky networks.

The design decision is that these are composed together on top of Axios adapters rather than being a wrapper around the Axios instance. An Axios adapter is the transport layer that Axios delegates the actual request to, so wrapping the adapter is where you can intercept a request before it goes out and a response before it comes back, without touching how you call Axios. A cache has to sit at that layer, because a cached response must never reach the network, and a throttle has to sit there too, because it has to see the request before it is sent.

The README also picks out a second reason for the design, the TypeScript story. The request config is module-augmented for the feature flags and overrides the enhancers add, so the extra options are typed on the Axios request config rather than living in a separate options object that you have to thread through your own code.

The package is MIT licensed, published on npm as axios-extensions, with the primary language TypeScript, the default branch on master, and the last push on 2026-09-05.

## The v3 to v4 jump, and the axios floor

Two things in the README are about compatibility, and both are worth reading before you install.

The first is that the README targets 4.x. If you are looking for the older documentation, the repository keeps a v3.1.7 branch with the previous docs, and there is a MIGRATION.md for people moving from v3.

The release history explains why that migration document exists. The versions are v3.1.6 in October 2022, v3.1.7 in May 2023, and then v4.0.0 on 2026-04-04, which is nearly three years of nothing in between and then a major version. The package manifest in the repository is at 4.0.0, so the 4.x line is the current one and the 3.x line is what most existing installations are still running.

The second is the compatibility floor. Node.js 18 or later is required, and axios 1.0.0 or later. The README adds a specific exclusion: axios v0.19.0 is not supported due to an Axios custom config issue, with a link to the axios pull request that describes it. So the library is not usable with the axios versions that many projects still had, and the reason is a defect in axios rather than a choice by the library.

That combination, a three-year gap and a jump to a new major version, tells you that the 4.x rewrite changed the API. The migration document is the place to find out how.

## Composing the pipeline, and why the order matters

The quick start builds one adapter out of all three enhancers, and the order in that snippet is the design.

```ts
import axios from 'axios';
import {
	cacheAdapterEnhancer,
	throttleAdapterEnhancer,
	retryAdapterEnhancer,
} from 'axios-extensions';

const adapter = retryAdapterEnhancer(
	throttleAdapterEnhancer(
		cacheAdapterEnhancer(axios.defaults.adapter!),
		{ threshold: 1000 },
	),
	{ times: 2 },
);

export const http = axios.create({
	baseURL: '/api',
	adapter,
});
```

The base is the default adapter, and each enhancer wraps the one before it, so the request travels inward through retry, then throttle, then cache, and the response comes back outward in the reverse order. Read that way, the semantics fall out. A retry happens at the outermost layer, so a retried request passes through the throttle and the cache again, and a request that was throttled or served from the cache is never retried. The cache is innermost, closest to the network, so it only ever sees requests that have not been throttled away and it holds the result of the actual network call.

The README also gives three composition presets, and they are worth copying because they are the shapes most applications need. Cache only. Cache plus throttle, described as good for list and search pages. And the full resilience pipeline, which is the snippet above.

Overrides work per request, on the same Axios call, with forceUpdate, threshold and retryTimes:

```ts
await http.get('/users', {
	forceUpdate: true,
	threshold: 3000,
	retryTimes: 3,
});
```

So a single client can have a global policy of cache, throttle and retry, and an individual call can opt out of the cache, wait longer for a shared in-flight request, or ask for more retries.

## Caching, and the key generation trap

The cache enhancer is the one with the sharp edges, and the README documents them well.

By default, if config.cache is set it is used as an override, and otherwise GET requests are cached. The default cache store is a small LRU with a five minute time to live and a maximum of a hundred entries, which comes from the tiny-lru package, the only runtime dependency of the library.

Three options shape it. cacheable decides which requests are cacheable, keyGenerator decides the key, and defaultCache replaces the store.

The key generator is where the trap is, and the README states it plainly: config.url alone is only safe when query params and body do not change response semantics. Otherwise you have to include params and, for non-GET requests, data in the key. The example in the README shows a key generator of method and url only, and it is labelled as safe only when params and body do not affect the response.

That warning is worth repeating because the failure is silent. A cache keyed on the URL alone will serve the response for /users?page=1 to a request for /users?page=2, and nothing in the output will look wrong. The override that fixes it is small, but you have to know to add it:

```ts
const adapter = cacheAdapterEnhancer(axios.defaults.adapter!, {
	cacheable: config => config.method === 'get' || config.method === 'post',
	keyGenerator: config => `${config.method}:${config.url}`,
});
```

Note that the default is GET only, and a POST is only cached if you opt in explicitly. And the per-request escape hatch is forceUpdate, which lets a single call bypass the cache when you know the cached value is stale.

## Throttling: the same promise, handed back

The throttle enhancer addresses a different problem from the cache, and the difference is in what it returns.

The cache stores a response, so the second caller gets a value from the store. The throttle does not store responses, it hands back the same in-flight promise. Requests within the threshold, which defaults to 1000 milliseconds, reuse the in-flight or previous promise, and non-GET requests pass through untouched. The intended case is a burst of identical GETs triggered by rapid user interface interaction, where three clicks in a second should produce one network call rather than three.

The signature makes the shape clear: it takes an adapter, an optional threshold, and an optional cache whose value type is a timestamp and an optional promise, so the throttle keeps just enough state to know whether the last call is still fresh.

The threshold is overridable per request, and the README's example sets it to 3000 for a single call, which is the case where one particular request needs to wait longer for a shared in-flight call than the global setting allows.

The scoping to GET only is what makes the enhancer safe to leave in a pipeline. A throttle on a POST would swallow writes, and a retried POST would be a duplicated write, so the library keeps both enhancers out of the way of anything that is not a read.

## Retries, and the TypeScript augmentation

The retry enhancer is the smallest of the three: it takes an adapter and an optional times, and it retries failed requests up to that many times, with two as the default. The per-request override is retryTimes.

```ts
await http.get('/critical-endpoint', { retryTimes: 3 });
```

The TypeScript side is the part that makes the library pleasant to use, and it is short. The package augments the Axios request types for four fields, cache, forceUpdate, threshold and retryTimes, which means those options appear on the request config everywhere an Axios request is typed, without a cast and without a wrapper type.

The README also notes a TypeScript detail that is easy to trip over: when importing shared cache contracts in TypeScript, use type-only imports. That is the standard advice for type-only symbols, and it matters here because the cache interface is a type and importing it as a value pulls the module in at run time, which is the kind of thing that breaks a build for no visible reason.

The combination of a typed config, per-request overrides and a single composable adapter means the library disappears into the client. Once the pipeline is built, a call site that wants a fresh value or more retries just says so, and nothing about the call site has to change when you add or remove an enhancer from the pipeline.

## The build, the tests and the release machinery

The package manifest is worth reading, because it says how the library is built and published.

The build script cleans the output directories and then runs the type build and the JavaScript build, and the JavaScript build runs Vite four times, once per output format: CommonJS, ES modules, UMD and minified UMD. The browser build is what the README documents loading from a content delivery network, exposed as window['axios-extensions']. The types are produced separately with tsc against a second tsconfig, tsconfig.types.json, so the declaration output and the JavaScript output are configured independently.

The published files are the esm, lib, dist and src directories, so the sources ship alongside the builds, and sideEffects is set to false, which lets a bundler drop the module entirely if nothing imports it.

The test setup is Vitest, with the main test script running the suite with coverage and a test:pure variant that runs without it. ESLint runs over src/ with a flat configuration in eslint.config.mjs, and Husky is installed by the prepare script, so the hooks are set up when the package is installed. Releases go through semantic-release, with the configuration in .releaserc.json, and the version in the manifest is therefore written by the tool rather than by a person.

The top-level listing has the same set of conventions: an .editorconfig, a .npmignore, a .github/ directory, an AGENTS.md, a MIGRATION.md and the two TypeScript configurations. The single runtime dependency, tiny-lru, is the only thing a consumer pulls in beyond axios itself, which is what the README means by lightweight.

## Conclusion

Adopt axios-extensions if your Axios client has the three problems it addresses, duplicate reads during a burst of interaction, a request that fails on a flaky link, and the same GET issued twice, because each enhancer is an adapter wrapper you compose in one pipeline and override per request. Do not adopt it if you are still on axios 0.19, because the README excludes that version over a custom config issue, and read MIGRATION.md first if you are moving from the 3.x line, since the README now targets 4.x. Before you enable the cache on anything but GET, look at how the key is generated, because the default key ignores params and data and the README is explicit that this is only safe when the body does not change the response.

## FAQ

### What does axios-extensions add to Axios?

Three composable adapter enhancers: cacheAdapterEnhancer for request-level response caching, throttleAdapterEnhancer to dedupe and throttle GET requests inside a time window, and retryAdapterEnhancer to retry failed requests. They wrap the Axios adapter rather than replacing Axios, and each can be overridden per request.

### Which versions of Node.js and axios does axios-extensions support?

Node.js 18 or later and axios 1.0.0 or later. The README excludes axios v0.19.0 specifically, because of an Axios custom config issue, and the repository carries a MIGRATION.md for users coming from the 3.x line, since the README now targets 4.x.

### How do I compose the cache, throttle and retry enhancers?

Wrap them around the default adapter in a pipeline, with the retry enhancer outermost, then the throttle, then the cache innermost, and pass the result to axios.create as the adapter. The README gives three presets for the common shapes, and each enhancer accepts its own options, such as a threshold for the throttle and a number of times for the retry.

### What is the danger of the default cache key in axios-extensions?

The default key is based on the request URL, and the README warns that config.url alone is only safe when query params and body do not change response semantics. Otherwise you have to include params and, for non-GET requests, data in the key, and the failure otherwise is a stale response served silently.

## Sources

- [Issues](https://github.com/kuitos/axios-extensions/issues)
- [kuitos/axios-extensions on GitHub](https://github.com/kuitos/axios-extensions)
- [License: MIT](https://github.com/kuitos/axios-extensions/blob/master/LICENSE)
- [README](https://github.com/kuitos/axios-extensions/blob/master/README.md)
- [Releases](https://github.com/kuitos/axios-extensions/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kuitos-axios-extensions
