https://github.com/code-y02/express-route-cache
Express route cache middleware for Node.js. Lightweight caching layer for Express Router with TTL support and in-memory caching.
https://github.com/code-y02/express-route-cache
caching express-router expressjs node-cache redis routing typescript
Last synced: 18 days ago
JSON representation
Express route cache middleware for Node.js. Lightweight caching layer for Express Router with TTL support and in-memory caching.
- Host: GitHub
- URL: https://github.com/code-y02/express-route-cache
- Owner: CODE-Y02
- License: mit
- Created: 2025-10-11T07:21:38.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2026-04-25T13:48:09.000Z (18 days ago)
- Last Synced: 2026-04-25T15:07:12.781Z (18 days ago)
- Topics: caching, express-router, expressjs, node-cache, redis, routing, typescript
- Language: TypeScript
- Homepage: https://code-y02.github.io/express-route-cache/
- Size: 408 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README
⚡ @express-route-cache
TanStack Query for the Backend
Production-grade, drop-in route caching for Express.js with O(1) invalidation, SWR, and Stampede Protection.
[](https://www.typescriptlang.org/)
[](https://www.npmjs.com/package/@express-route-cache/core)
[](https://opensource.org/licenses/MIT)
[](https://github.com/CODE-Y02/express-route-cache/actions)
[](https://chatgpt.com/?q=Analyze+this+library.+Docs:+https://code-y02.github.io/express-route-cache/+GitHub:+https://github.com/CODE-Y02/express-route-cache+NPM:+https://www.npmjs.com/package/@express-route-cache/core)
[](https://claude.ai/new?q=Help+me+with+this+library.+Docs:+https://code-y02.github.io/express-route-cache/+LLM+Context:+https://code-y02.github.io/express-route-cache/llms.txt)
[](https://code-y02.github.io/express-route-cache/llms.txt)
> [!IMPORTANT]
> **Production-Grade & Distributed**: Unlike legacy `express-route-cache` or `apicache` packages, this library is built for high-scale, distributed environments. It features O(1) invalidation, full Redis/Memcached support, and native binary handling.
### 😭 The Problem with Express Caching
Every existing Express caching middleware (`apicache`, `route-cache`, `cache-express`) shares the same fatal production pain-points:
- ❌ **O(N) Invalidation:** When a user updates their profile, traditional libraries have to `SCAN` the entire Redis instance to find and delete all keys that match `/users/123/*`. This hangs the Node event loop and brings down databases.
- ❌ **No Stale-While-Revalidate (SWR):** Cache expires -> next user waits 300ms for a fresh database pull.
- ❌ **Thundering Herds:** A viral post expires from the cache. 1,000 requests hit Express. Your database gets 1,000 identical queries simultaneously and melts.
### 🚀 The Solution
Meet `@express-route-cache`. We brought the modern conveniences of frontend data-fetching (like TanStack/React Query) to your backend Express APIs.
| Feature | Existing Packages | This Library |
| -------------------------- | ----------------- | -------------------------------------------------- |
| **Invalidation** | ❌ `SCAN` / `DEL` | ✅ **O(1) Epoch `INCR`** (Instant, zero blocking) |
| **Stale-While-Revalidate** | ❌ | ✅ **Instant Stale Delivery** + Background Refresh |
| **Stampede Protection** | ❌ | ✅ **Request Coalescing** (1,000 reqs = 1 DB call) |
| **Adapters** | ❌ Locked to one | ✅ **Memory, Redis (ioredis), Memcached (memjs)** |
| **Binary Support** | ❌ JSON only | ✅ **Automatic** (Images, PDFs, Buffers) |
| **Header Preservation** | ❌ Stripped | ✅ **Automatic** (CORS, Custom Headers) |
| **Standalone Fetch** | ❌ | ✅ **`cache.fetch()`** (Manual data caching) |
| **Retries** | ❌ | ✅ **Exponential Backoff** (Built-in) |
| **DX** | ❌ Callbacks | ✅ **Modern API** (`staleTime`, `autoInvalidate`) |
---
## 📦 Installation
```bash
# Core package (includes Memory adapter out of the box)
npm install @express-route-cache/core
# Want distributed caching? Add an adapter:
npm install @express-route-cache/redis ioredis
npm install @express-route-cache/memcached memjs
```
## 🛠️ Quick Start
```ts
import express from "express";
import { createCache, createMemoryAdapter } from "@express-route-cache/core";
const app = express();
// 1. Initialize the Cache
const cache = createCache({
adapter: createMemoryAdapter(),
staleTime: 60, // Fresh for 60 seconds (Instant HIT)
gcTime: 300, // Kept stale for 5 more minutes
swr: true, // Enable Stale-While-Revalidate!
});
// 2. Cache globally (Only caches GET requests automatically)
app.use(cache.middleware());
// 3. Or override per-route
app.get("/users/:id", cache.route({ staleTime: 120 }), getUser);
// 4. Invalidate instantly upon mutation (POST/PUT/DELETE)
app.post("/users", cache.invalidate("/users"), createUser);
```
---
## 🧠 Core Concepts
### 1. Fresh vs Stale (TanStack-Inspired)
We use a two-tier timing model:
1. **`staleTime`**: The duration data is considered "fresh". The cache returns the value instantly.
2. **`gcTime`**: The duration data remains in the cache _after_ it becomes stale.
If `swr: true` is enabled:
- **Fresh**: ⚡ Instant HIT.
- **Stale**: 🔄 Instant HIT (returns stale data) + Background revalidation triggers automatically.
- **Expired/Evicted**: ⏳ MISS (handler runs, updates cache).
### 2. O(1) Epoch Invalidation
Instead of slow `Set` key tracking, we use **Epoch Versioning**. Every route pattern has a tiny numeric counter in the cache.
When you cache `/users/123`, the key looks like this: `erc:GET:/users/123|v:/users=5|v:/users/:id=2`.
To invalidate the entire `/users` tree, we simply increment the `/users` counter to `6`. All future requests generate brand new keys, immediately abandoning the old data. It requires zero key scanning.
### 3. Stampede Protection
If 5,000 users request `/viral-post` at the exact same millisecond the cache expires, `@express-route-cache` steps in. It holds the 4,999 connection promises in memory and executes your Express handler exactly **one** time. Once the database returns the data, all 5,000 connections are resolved simultaneously.
> 📚 **Deep Dive:** Want to know _why_ we didn't use Redis distributed locks? Or how exactly the `INCR` command guarantees O(1) performance? Read our [Architecture & Trade-offs](./ARCHITECTURE.md) and [Comparison with TanStack Query](./docs/guide/comparison.md) documents.
---
## 📖 API Reference
### `createCache(config)`
| Option | Type | Default | Description |
| ---------------- | ------------- | --------- | ------------------------------------------------------------------------------- |
| `adapter` | `CacheClient` | — | **Required**. Memory, Redis, or Memcached adapter. |
| `staleTime` | `number` | `60` | Seconds data stays fresh. |
| `gcTime` | `number` | `300` | Seconds stale data stays in cache. |
| `swr` | `boolean` | `false` | Enable background revalidation. |
| `stampede` | `boolean` | `true` | Prevent "thundering herd" by coalescing requests. |
| `vary` | `string[]` | `[]` | Headers to namespace caches (e.g. `['authorization']`). |
| `sortQuery` | `boolean` | `false` | Sort query params deterministically (`?a=1&b=2` equals `?b=2&a=1`) |
| `maxBodySize` | `number` | `2097152` | Max response body size in bytes to cache (default: 2MB). Prevents memory leaks. |
| `autoInvalidate` | `boolean` | `false` | Automatically invalidate route patterns on successful `POST/PUT/DELETE`. |
| `retry` | `number` | `0` | Number of retries for failed fetches (fetch only). |
| `enabled` | `boolean` | `true` | Toggle caching globally. |
Returns `{ middleware(), route(), fetch(), invalidate(), invalidateRoute(), adapter }`.
### `cache.route(opts)`
Per-route middleware. Accepts all configuration options (like `staleTime` and `retry`) as overrides for a specific endpoint.
**Custom Keys:**
You can provide a custom string or a function to generate the cache key.
```ts
app.get(
"/user",
cache.route({
key: (req) => `user-${req.user.id}`,
}),
handler,
);
```
### `cache.fetch(key, fetcher, opts)`
Standalone method for manual data caching. Includes full SWR and Stampede Protection.
```ts
const data = await cache.fetch(
"my-key",
async () => {
return await db.users.findMany();
},
{ staleTime: 60, swr: true, retry: 3 },
);
```
### `cache.invalidate(...routePatterns)`
Express middleware to invalidate particular routes.
`app.post('/article', cache.invalidate('/articles'), handler)`
### `cache.invalidateRoute(...routePatterns)`
Programmatic invalidation for use inside services, cron jobs, or webhooks.
`await cache.invalidateRoute('/users/123');`
---
## 🛠️ Advanced Features
### Binary Data Support
Unlike most Express caching libraries that only handle JSON strings, `@express-route-cache` supports binary responses out of the box. You can cache images, PDFs, and ZIP files without corruption.
### Advanced Streaming Support
Unlike many middleware libraries that only work with `res.json()` or `res.send()`, `@express-route-cache` hooks into the low-level `res.write()` and `res.end()` streams. This allows it to cache standard JSON, chunked transfers, and large binary files.
### Smart Invalidation
Invalidation (via `cache.invalidate()` or `autoInvalidate: true`) is **post-response**. This means we only increment the route version if your handler finishes successfully (2xx). This prevents "Cache Zombies" where stale data is re-cached due to race conditions during database updates.
### Comprehensive Header Preservation
We use `res.getHeaders()` to capture the full response state, filtering only for ephemeral headers (like `Set-Cookie` or `X-Express-*`), ensuring a perfect high-fidelity replay of the original response including CORS and custom headers.
---
## 🔌 Adapters
### Memory (Built-in)
For single-process apps and local development.
```ts
import { createMemoryAdapter } from "@express-route-cache/core";
const adapter = createMemoryAdapter(600); // Default strict TTL fallback: 600s
```
### Redis (`@express-route-cache/redis`)
Highly recommended for production.
```ts
import { createRedisAdapter } from "@express-route-cache/redis";
// Connect via URL
const adapter = createRedisAdapter({ url: "redis://localhost:6379" });
// OR reuse your existing application client safely (we won't double-close it!)
const adapter = createRedisAdapter({ client: myGlobalIoredisClient });
```
### Memcached (`@express-route-cache/memcached`)
Perfect for high-throughput, pure KV caching.
```ts
import { createMemcachedAdapter } from "@express-route-cache/memcached";
const adapter = createMemcachedAdapter({ servers: "localhost:11211" });
```
---
## 🔍 HTTP Headers
We automatically append headers for CDN and debugging visibility:
- `X-Cache`: `HIT` | `MISS` | `STALE`
- `Age`: How many seconds old the data is.
- `Cache-Control`: Respects your `staleTime` (e.g. `public, max-age=60`). **Note:** If your handler sets its own `Cache-Control` (e.g., `private`), this library respects it and won't overwrite it.
---
## 🛠️ Advanced Features
### Binary Data Support
Unlike most Express caching libraries that only handle JSON strings, `@express-route-cache` supports binary responses out of the box. You can cache images, PDFs, and ZIP files without corruption.
### Smart Invalidation
Invalidation (via `cache.invalidate()` or `autoInvalidate: true`) is **post-response**. This means we only increment the route version if your handler finishes successfully (2xx). This prevents "Cache Zombies" where stale data is re-cached due to race conditions during database updates.
---
## 🚀 Release Channels & Versioning
This project uses an automated CI/CD pipeline (via [Changesets](https://github.com/changesets/changesets)) to manage NPM distributions directly from GitHub branches.
Depending on your stability needs, you can install from different channels using NPM `dist-tags`:
| Channel | NPM Tag | Command | GitHub Branch | Description |
| :---------- | :---------- | :----------------------------------------- | :------------------ | :--------------------------------------------------------------------------------- |
| **Stable** | `@latest` | `npm i @express-route-cache/core@latest` | `main` | Production-ready. This is the default. |
| **Beta/RC** | `@next` | `npm i @express-route-cache/core@next` | `next` | Cutting-edge features currently in development. |
| **Legacy** | `@vX.Y-lts` | `npm i @express-route-cache/core@v0.1-lts` | `v*` (e.g., `v0.1`) | Old architectural versions maintained solely for critical security/hotfix patches. |
### For Contributors & Maintainers
If you are contributing to this project or managing releases, please read our [Contributing Guide](CONTRIBUTING.md#release-channels--versioning) to understand how we use the `main`, `next`, and `v*` branches to automatically deploy updates to NPM.
---
## 👨💻 Author
Created and maintained by **Yatharth Lakhate**.
If you have questions, feedback, or need to report a vulnerability privately, you can reach me directly:
- **LinkedIn**: [Yatharth Lakhate](https://www.linkedin.com/in/yatharth-lakhate/)
- **X (Twitter)**: [@Yatharth_L](https://x.com/Yatharth_L)
---
## License
MIT