npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@express-route-cache/core

v0.1.3

Published

Core caching functionality for Express routes

Readme

TypeScript NPM Version License: MIT

😭 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) | | DX | ❌ Callbacks | ✅ Modern API (staleTime, gcTime, swr: true) |


📦 Installation

# 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

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 document for detailed diagrams.


📖 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) | | enabled | boolean | true | Toggle caching globally. |

Returns { middleware(), route(), invalidate(), invalidateRoute(), adapter }.

cache.route(opts)

Per-route middleware. Accepts all configuration options (like staleTime) as overrides for a specific endpoint.

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');


🔌 Adapters

Memory (Built-in)

For single-process apps and local development.

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.

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.

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).

License

MIT