{"id":46387611,"url":"https://github.com/code-y02/express-route-cache","last_synced_at":"2026-04-25T20:02:19.892Z","repository":{"id":342192067,"uuid":"1074121432","full_name":"CODE-Y02/express-route-cache","owner":"CODE-Y02","description":"Express route cache middleware for Node.js.  Lightweight caching layer for Express Router with TTL support and in-memory caching.","archived":false,"fork":false,"pushed_at":"2026-04-25T13:48:09.000Z","size":418,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-25T15:07:12.781Z","etag":null,"topics":["caching","express-router","expressjs","node-cache","redis","routing","typescript"],"latest_commit_sha":null,"homepage":"https://code-y02.github.io/express-route-cache/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CODE-Y02.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-11T07:21:38.000Z","updated_at":"2026-04-25T13:48:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/CODE-Y02/express-route-cache","commit_stats":null,"previous_names":["code-y02/express-route-cache"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/CODE-Y02/express-route-cache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CODE-Y02%2Fexpress-route-cache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CODE-Y02%2Fexpress-route-cache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CODE-Y02%2Fexpress-route-cache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CODE-Y02%2Fexpress-route-cache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CODE-Y02","download_url":"https://codeload.github.com/CODE-Y02/express-route-cache/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CODE-Y02%2Fexpress-route-cache/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32274987,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T18:29:39.964Z","status":"ssl_error","status_checked_at":"2026-04-25T18:29:32.149Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caching","express-router","expressjs","node-cache","redis","routing","typescript"],"created_at":"2026-03-05T08:00:42.211Z","updated_at":"2026-04-25T20:02:19.885Z","avatar_url":"https://github.com/CODE-Y02.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch1\u003e⚡ @express-route-cache\u003c/h1\u003e\n  \u003cp\u003e\u003cstrong\u003eTanStack Query for the Backend\u003c/strong\u003e\u003c/p\u003e\n  \u003cp\u003eProduction-grade, drop-in route caching for Express.js with O(1) invalidation, SWR, and Stampede Protection.\u003c/p\u003e\n\n[![TypeScript](https://img.shields.io/badge/TypeScript-100%25-blue)](https://www.typescriptlang.org/)\n[![NPM Version](https://img.shields.io/npm/v/@express-route-cache/core.svg)](https://www.npmjs.com/package/@express-route-cache/core)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![CI](https://github.com/CODE-Y02/express-route-cache/actions/workflows/release.yml/badge.svg)](https://github.com/CODE-Y02/express-route-cache/actions)\n[![ChatGPT](https://img.shields.io/badge/Chat--GPT-Support-74aa9c?logo=openai\u0026logoColor=white)](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)\n[![Claude](https://img.shields.io/badge/Claude-Support-d97757?logo=anthropic\u0026logoColor=white)](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)\n[![Cursor](https://img.shields.io/badge/Cursor-Context-519aba?logo=cursor\u0026logoColor=white)](https://code-y02.github.io/express-route-cache/llms.txt)\n\n\u003c/div\u003e\n\n\u003chr /\u003e\n\n\u003e [!IMPORTANT]\n\u003e **Production-Grade \u0026 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.\n\n\u003chr /\u003e\n\n### 😭 The Problem with Express Caching\n\nEvery existing Express caching middleware (`apicache`, `route-cache`, `cache-express`) shares the same fatal production pain-points:\n\n- ❌ **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.\n- ❌ **No Stale-While-Revalidate (SWR):** Cache expires -\u003e next user waits 300ms for a fresh database pull.\n- ❌ **Thundering Herds:** A viral post expires from the cache. 1,000 requests hit Express. Your database gets 1,000 identical queries simultaneously and melts.\n\n### 🚀 The Solution\n\nMeet `@express-route-cache`. We brought the modern conveniences of frontend data-fetching (like TanStack/React Query) to your backend Express APIs.\n\n| Feature                    | Existing Packages | This Library                                       |\n| -------------------------- | ----------------- | -------------------------------------------------- |\n| **Invalidation**           | ❌ `SCAN` / `DEL` | ✅ **O(1) Epoch `INCR`** (Instant, zero blocking)  |\n| **Stale-While-Revalidate** | ❌                | ✅ **Instant Stale Delivery** + Background Refresh |\n| **Stampede Protection**    | ❌                | ✅ **Request Coalescing** (1,000 reqs = 1 DB call) |\n| **Adapters**               | ❌ Locked to one  | ✅ **Memory, Redis (ioredis), Memcached (memjs)**  |\n| **Binary Support**         | ❌ JSON only      | ✅ **Automatic** (Images, PDFs, Buffers)           |\n| **Header Preservation**    | ❌ Stripped       | ✅ **Automatic** (CORS, Custom Headers)            |\n| **Standalone Fetch**       | ❌                | ✅ **`cache.fetch()`** (Manual data caching)       |\n| **Retries**                | ❌                | ✅ **Exponential Backoff** (Built-in)              |\n| **DX**                     | ❌ Callbacks      | ✅ **Modern API** (`staleTime`, `autoInvalidate`)  |\n\n---\n\n## 📦 Installation\n\n```bash\n# Core package (includes Memory adapter out of the box)\nnpm install @express-route-cache/core\n\n# Want distributed caching? Add an adapter:\nnpm install @express-route-cache/redis ioredis\nnpm install @express-route-cache/memcached memjs\n```\n\n## 🛠️ Quick Start\n\n```ts\nimport express from \"express\";\nimport { createCache, createMemoryAdapter } from \"@express-route-cache/core\";\n\nconst app = express();\n\n// 1. Initialize the Cache\nconst cache = createCache({\n  adapter: createMemoryAdapter(),\n  staleTime: 60, // Fresh for 60 seconds (Instant HIT)\n  gcTime: 300, // Kept stale for 5 more minutes\n  swr: true, // Enable Stale-While-Revalidate!\n});\n\n// 2. Cache globally (Only caches GET requests automatically)\napp.use(cache.middleware());\n\n// 3. Or override per-route\napp.get(\"/users/:id\", cache.route({ staleTime: 120 }), getUser);\n\n// 4. Invalidate instantly upon mutation (POST/PUT/DELETE)\napp.post(\"/users\", cache.invalidate(\"/users\"), createUser);\n```\n\n---\n\n## 🧠 Core Concepts\n\n### 1. Fresh vs Stale (TanStack-Inspired)\n\nWe use a two-tier timing model:\n\n1. **`staleTime`**: The duration data is considered \"fresh\". The cache returns the value instantly.\n2. **`gcTime`**: The duration data remains in the cache _after_ it becomes stale.\n\nIf `swr: true` is enabled:\n\n- **Fresh**: ⚡ Instant HIT.\n- **Stale**: 🔄 Instant HIT (returns stale data) + Background revalidation triggers automatically.\n- **Expired/Evicted**: ⏳ MISS (handler runs, updates cache).\n\n### 2. O(1) Epoch Invalidation\n\nInstead of slow `Set` key tracking, we use **Epoch Versioning**. Every route pattern has a tiny numeric counter in the cache.\nWhen you cache `/users/123`, the key looks like this: `erc:GET:/users/123|v:/users=5|v:/users/:id=2`.\n\nTo 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.\n\n### 3. Stampede Protection\n\nIf 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.\n\n\u003e 📚 **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 \u0026 Trade-offs](./ARCHITECTURE.md) and [Comparison with TanStack Query](./docs/guide/comparison.md) documents.\n\n---\n\n## 📖 API Reference\n\n### `createCache(config)`\n\n| Option           | Type          | Default   | Description                                                                     |\n| ---------------- | ------------- | --------- | ------------------------------------------------------------------------------- |\n| `adapter`        | `CacheClient` | —         | **Required**. Memory, Redis, or Memcached adapter.                              |\n| `staleTime`      | `number`      | `60`      | Seconds data stays fresh.                                                       |\n| `gcTime`         | `number`      | `300`     | Seconds stale data stays in cache.                                              |\n| `swr`            | `boolean`     | `false`   | Enable background revalidation.                                                 |\n| `stampede`       | `boolean`     | `true`    | Prevent \"thundering herd\" by coalescing requests.                               |\n| `vary`           | `string[]`    | `[]`      | Headers to namespace caches (e.g. `['authorization']`).                         |\n| `sortQuery`      | `boolean`     | `false`   | Sort query params deterministically (`?a=1\u0026b=2` equals `?b=2\u0026a=1`)              |\n| `maxBodySize`    | `number`      | `2097152` | Max response body size in bytes to cache (default: 2MB). Prevents memory leaks. |\n| `autoInvalidate` | `boolean`     | `false`   | Automatically invalidate route patterns on successful `POST/PUT/DELETE`.        |\n| `retry`          | `number`      | `0`       | Number of retries for failed fetches (fetch only).                              |\n| `enabled`        | `boolean`     | `true`    | Toggle caching globally.                                                        |\n\nReturns `{ middleware(), route(), fetch(), invalidate(), invalidateRoute(), adapter }`.\n\n### `cache.route(opts)`\n\nPer-route middleware. Accepts all configuration options (like `staleTime` and `retry`) as overrides for a specific endpoint.\n\n**Custom Keys:**\nYou can provide a custom string or a function to generate the cache key.\n\n```ts\napp.get(\n  \"/user\",\n  cache.route({\n    key: (req) =\u003e `user-${req.user.id}`,\n  }),\n  handler,\n);\n```\n\n### `cache.fetch(key, fetcher, opts)`\n\nStandalone method for manual data caching. Includes full SWR and Stampede Protection.\n\n```ts\nconst data = await cache.fetch(\n  \"my-key\",\n  async () =\u003e {\n    return await db.users.findMany();\n  },\n  { staleTime: 60, swr: true, retry: 3 },\n);\n```\n\n### `cache.invalidate(...routePatterns)`\n\nExpress middleware to invalidate particular routes.\n`app.post('/article', cache.invalidate('/articles'), handler)`\n\n### `cache.invalidateRoute(...routePatterns)`\n\nProgrammatic invalidation for use inside services, cron jobs, or webhooks.\n`await cache.invalidateRoute('/users/123');`\n\n---\n\n## 🛠️ Advanced Features\n\n### Binary Data Support\n\nUnlike 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.\n\n### Advanced Streaming Support\n\nUnlike 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.\n\n### Smart Invalidation\n\nInvalidation (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.\n\n### Comprehensive Header Preservation\n\nWe 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.\n\n---\n\n## 🔌 Adapters\n\n### Memory (Built-in)\n\nFor single-process apps and local development.\n\n```ts\nimport { createMemoryAdapter } from \"@express-route-cache/core\";\nconst adapter = createMemoryAdapter(600); // Default strict TTL fallback: 600s\n```\n\n### Redis (`@express-route-cache/redis`)\n\nHighly recommended for production.\n\n```ts\nimport { createRedisAdapter } from \"@express-route-cache/redis\";\n\n// Connect via URL\nconst adapter = createRedisAdapter({ url: \"redis://localhost:6379\" });\n\n// OR reuse your existing application client safely (we won't double-close it!)\nconst adapter = createRedisAdapter({ client: myGlobalIoredisClient });\n```\n\n### Memcached (`@express-route-cache/memcached`)\n\nPerfect for high-throughput, pure KV caching.\n\n```ts\nimport { createMemcachedAdapter } from \"@express-route-cache/memcached\";\nconst adapter = createMemcachedAdapter({ servers: \"localhost:11211\" });\n```\n\n---\n\n## 🔍 HTTP Headers\n\nWe automatically append headers for CDN and debugging visibility:\n\n- `X-Cache`: `HIT` | `MISS` | `STALE`\n- `Age`: How many seconds old the data is.\n- `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.\n\n---\n\n## 🛠️ Advanced Features\n\n### Binary Data Support\n\nUnlike 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.\n\n### Smart Invalidation\n\nInvalidation (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.\n\n---\n\n## 🚀 Release Channels \u0026 Versioning\n\nThis project uses an automated CI/CD pipeline (via [Changesets](https://github.com/changesets/changesets)) to manage NPM distributions directly from GitHub branches.\n\nDepending on your stability needs, you can install from different channels using NPM `dist-tags`:\n\n| Channel     | NPM Tag     | Command                                    | GitHub Branch       | Description                                                                        |\n| :---------- | :---------- | :----------------------------------------- | :------------------ | :--------------------------------------------------------------------------------- |\n| **Stable**  | `@latest`   | `npm i @express-route-cache/core@latest`   | `main`              | Production-ready. This is the default.                                             |\n| **Beta/RC** | `@next`     | `npm i @express-route-cache/core@next`     | `next`              | Cutting-edge features currently in development.                                    |\n| **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. |\n\n### For Contributors \u0026 Maintainers\n\nIf 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.\n\n---\n\n## 👨‍💻 Author\n\nCreated and maintained by **Yatharth Lakhate**.\nIf you have questions, feedback, or need to report a vulnerability privately, you can reach me directly:\n\n- **LinkedIn**: [Yatharth Lakhate](https://www.linkedin.com/in/yatharth-lakhate/)\n- **X (Twitter)**: [@Yatharth_L](https://x.com/Yatharth_L)\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcode-y02%2Fexpress-route-cache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcode-y02%2Fexpress-route-cache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcode-y02%2Fexpress-route-cache/lists"}