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-memorize

v1.4.0

Published

In-memory cache middleware for Express.js. Caches GET responses with optional TTL — zero dependencies, fully typed.

Readme

express-memorize


Features

  • Caches GET responses automatically when status code is 2xx
  • Per-route TTL override
  • Event hooks: set, delete, expire
  • Cache inspection and invalidation API
  • Hit counter per cache entry
  • X-Cache: HIT | MISS | BYPASS response header
  • Zero runtime dependencies
  • Full TypeScript support

Installation

npm install express-memorize

Quick Start

import express from 'express';
import { memorize } from 'express-memorize';

const app = express();
const cache = memorize({ ttl: 30_000 }); // 30 seconds global TTL

app.get('/users', cache(), async (req, res) => {
  const users = await db.getUsers();
  res.json({ data: users });
});

app.listen(3000);

The first request computes the response normally. Every subsequent GET /users is served from memory until the TTL expires.

Usage

Global middleware

Apply the cache to the entire application with app.use(). Every GET route is cached automatically — non-GET requests are bypassed without any extra configuration.

const cache = memorize({ ttl: 60_000 });

app.use(cache()); // applies to all GET routes

app.get('/users',   (req, res) => { res.json({ data: users }) });
app.get('/products', (req, res) => { res.json({ data: products }) });
// POST, PUT, PATCH, DELETE routes are unaffected

Per-route cache

const cache = memorize({ ttl: 60_000 });

app.get('/products', cache(), (req, res) => {
  res.json({ data: products });
});

Per-route TTL override

const cache = memorize({ ttl: 60_000 }); // global: 60s

app.get('/users',    cache(),               handler); // 60s
app.get('/products', cache({ ttl: 10_000 }), handler); // override: 10s
app.get('/config',   cache({ ttl: 0 }),      handler); // no expiry

Cache invalidation

const cache = memorize({ ttl: 30_000 });

app.get('/users', cache(), (req, res) => {
  res.json({ data: users });
});

app.post('/users', (req, res) => {
  users.push(req.body);
  cache.delete('/users'); // invalidate after mutation
  res.status(201).json({ data: req.body });
});

Pattern-based invalidation

Use cache.deleteMatching(pattern) to remove all cache entries whose keys match a glob pattern. This is useful when you don't know the exact key — for example, when a URL may have different query strings.

// Cached keys: /api/users/abc123, /api/users/abc123?lang=es, /api/users/abc123?page=1
app.put('/users/:id', (req, res) => {
  users.update(req.params.id, req.body);

  // Remove all cached variants of this user, regardless of query params
  const deleted = cache.deleteMatching(`**/users/${req.params.id}*`);
  console.log(`${deleted} cache entries removed`);

  res.json({ ok: true });
});

Glob rules:

| Pattern | Behaviour | |---------|-----------| | * | Matches any sequence of characters within a single path segment (does not cross /) | | ** | Matches any sequence of characters across path segments (crosses /) | | ? | Matches any single character except / |

deleteMatching returns the number of entries removed and emits a delete event for each one.

Event hooks

const cache = memorize({ ttl: 30_000 });

cache.on('set', (e) => {
  console.log(`[cache] stored ${e.key} — expires in ${e.expiresAt ? e.expiresAt - Date.now() : '∞'}ms`);
});

cache.on('delete', (e) => {
  console.log(`[cache] deleted ${e.key}`);
});

cache.on('expire', (e) => {
  console.log(`[cache] expired ${e.key}`);
});

Inspect the cache

cache.get('/users');   // CacheInfo | null — single entry
cache.getAll();        // Record<string, CacheInfo> — all active entries

CacheInfo shape:

{
  key: string;
  body: unknown;
  statusCode: number;
  contentType: string;
  expiresAt: number | null;
  remainingTtl: number | null; // ms until expiry, null if no TTL
  hits: number;                // total times this key was requested
}

hits starts at 1 on the initial cache miss (when the entry is stored) and increments by 1 on every subsequent cache hit. It resets to 1 if the entry is evicted and re-cached.

// Example: monitoring hot keys
const entries = cache.getAll();
for (const [key, info] of Object.entries(entries)) {
  console.log(`${key} → ${info.hits} hits`);
}
// /users        → 42 hits
// /products     → 7 hits

Clear the cache

cache.delete('/users');                  // remove one entry
cache.deleteMatching('**/users/*');      // remove all /users/* entries
cache.clear();                           // remove all entries

API Reference

memorize(options?)

Creates a cache instance. Returns a Memorize object.

| Option | Type | Default | Description | |--------|------|---------|-------------| | ttl | number | undefined | Time-to-live in milliseconds. Omit for no expiry. |

cache(options?)

Returns an Express RequestHandler middleware. Can override the global TTL.

| Option | Type | Default | Description | |--------|------|---------|-------------| | ttl | number | global ttl | TTL override for this specific route. |

Cache management

| Method | Signature | Description | |--------|-----------|-------------| | get | (key: string) => CacheInfo \| null | Returns info for a cached key. | | getAll | () => Record<string, CacheInfo> | Returns all active cache entries. | | delete | (key: string) => boolean | Removes a single entry. Returns false if not found. | | deleteMatching | (pattern: string) => number | Removes all entries matching a glob pattern. Returns the count removed. | | clear | () => void | Removes all entries. |

Events

| Event | Payload | When | |-------|---------|------| | set | { type, key, body, statusCode, contentType, expiresAt } | A response is stored | | delete | { type, key } | cache.delete(), cache.deleteMatching(), or cache.clear() is called | | expire | { type, key } | TTL timer fires or lazy expiry is detected |

Response Headers

| Header | Value | Description | |--------|-------|-------------| | X-Cache | HIT | Response served from cache | | X-Cache | MISS | Response computed and stored | | X-Cache | BYPASS | Cache skipped — noCache: true was set for this route |

Behavior

  • Only GET requests are cached. All other methods bypass the middleware entirely.
  • Only responses with a 2xx status code are stored.
  • Each call to cache() returns an independent middleware handler, but all handlers created from the same memorize() instance share the same store.
  • Two separate memorize() calls produce independent stores.

License

MIT