{"id":44694710,"url":"https://github.com/mpfdavis/outputcache","last_synced_at":"2026-02-15T08:01:31.109Z","repository":{"id":57316973,"uuid":"50659322","full_name":"mpfdavis/outputcache","owner":"mpfdavis","description":"Cache api responses using Redis, Memcached or any cache provider for NodeJS","archived":false,"fork":false,"pushed_at":"2018-07-17T22:19:57.000Z","size":93,"stargazers_count":13,"open_issues_count":0,"forks_count":4,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-09-29T08:38:50.090Z","etag":null,"topics":["api","cache","express","lru-cache","memcached","middleware","nodejs","outputcache","react","redis-cache"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mpfdavis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-01-29T11:46:22.000Z","updated_at":"2022-03-08T08:21:51.000Z","dependencies_parsed_at":"2022-08-25T20:00:50.713Z","dependency_job_id":null,"html_url":"https://github.com/mpfdavis/outputcache","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mpfdavis/outputcache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpfdavis%2Foutputcache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpfdavis%2Foutputcache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpfdavis%2Foutputcache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpfdavis%2Foutputcache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mpfdavis","download_url":"https://codeload.github.com/mpfdavis/outputcache/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpfdavis%2Foutputcache/sbom","scorecard":{"id":662839,"data":{"date":"2025-08-11","repo":{"name":"github.com/mpfdavis/outputcache","commit":"91872f8d0cdd00372345f046746a3ebc380aff3d"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: ISC License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-21T16:45:43.512Z","repository_id":57316973,"created_at":"2025-08-21T16:45:43.512Z","updated_at":"2025-08-21T16:45:43.512Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29473358,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-15T06:58:05.414Z","status":"ssl_error","status_checked_at":"2026-02-15T06:58:05.085Z","response_time":118,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":["api","cache","express","lru-cache","memcached","middleware","nodejs","outputcache","react","redis-cache"],"created_at":"2026-02-15T08:01:13.868Z","updated_at":"2026-02-15T08:01:31.103Z","avatar_url":"https://github.com/mpfdavis.png","language":"JavaScript","readme":"# Outputcache\n\n[![Version](https://img.shields.io/npm/v/outputcache.svg)](https://www.npmjs.com/package/outputcache)\n[![License](https://img.shields.io/npm/l/outputcache.svg)](https://www.npmjs.com/package/outputcache)\n[![Downloads](https://img.shields.io/npm/dt/outputcache.svg)](https://www.npmjs.com/package/outputcache)\n[![Build Status](https://travis-ci.org/mpfdavis/outputcache.svg?branch=master)](https://travis-ci.org/mpfdavis/outputcache)\n[![Known Vulnerabilities](https://snyk.io/test/npm/outputcache/badge.svg)](https://snyk.io/test/npm/outputcache)\n[![Test Coverage](https://coveralls.io/repos/mpfdavis/outputcache/badge.svg?branch=master\u0026service=github)](https://coveralls.io/github/mpfdavis/outputcache?branch=master)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/315fc61665cb4871a55314cffad0c3f6)](https://www.codacy.com/app/mpfdavis/outputcache?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=mpfdavis/outputcache\u0026amp;utm_campaign=Badge_Grade)\n\nCache api responses, react and more using Redis, Memcached or any other cache provider. \n\n## Why?\n \nSimple middleware - it will cache the output and headers of each response. This makes it easy to create a highly scalable [Redis cache for your Node API](#using-an-alternative-cache-provider---redis) or simply boost the throughput of your Node application if using a heavier render engine such as React.\n\nOutputcache will honour the status, max-age, no-store, no-cache, private and stale-while-revalidate headers from your original response for ttl by default. This enables your services to dynamically dictate the ttl of each response using http rules. It is also highly configurable - [see API](#api).\n\n- Fast - returns original response directly from cache and uses optimised version of LRU cache by default (Maps)\n- Simple - honours all original headers, status codes and requires few code changes\n- Flexible - use any cache provider under the hood, in-process or remote such as Redis cache\n- Well tested - many unit tests, load tested and battle-tested in production\n\n\n## Installation\n```\nnpm install outputcache --save\n```\n\n\n## Dependencies\n\nOnly one - an optional local cache ('stale-lru-cache'). This was chosen as it outperforms alternatives in [benchmarks](https://github.com/cyberthom/stale-lru-cache/tree/master/benchmark/results) and enables you to get going quickly. You can easily override this with Redis or any other - [see API](#api). \n\n\n## Initialize\n\n```javascript\nconst OutputCache = require('outputcache');\nconst xoc = new OutputCache({options}); //see api below\n```\n\n\n## Usage\n\nThe following example places Outputcache before \"api.middleware\" - this ensures all cached responses return as soon as possible and avoid any subsequent data gathering or processing.\n\n### Cache select routes\n\n```javascript\nconst xoc = new OutputCache();\n\napp.get('/api/:channel', xoc.middleware, api.middleware, (req, res) =\u003e {    \n  res.set({'Cache-Control': 'max-age=600'});  \n  res.json({hello:'world'}); //will be hit once every 10 minutes \n});\n\napp.get('/', xoc.middleware, api.middleware, (req, res) =\u003e {  \n  res.set({'Cache-Control': 'max-age=600'});  \n  res.render('hello', {hello:'world'}); //will be hit once every 10 minutes \n});\n```\n\n### Cache all routes\n\n```javascript\nconst xoc = new OutputCache();\n\napp.use(xoc);\n\napp.get('/api/:channel', xoc.middleware, api.middleware, (req, res) =\u003e {    \n  res.set({'Cache-Control': 'max-age=600'});  \n  res.json({hello:'world'}); //will be hit once every 10 minutes \n});\n```\n\n### Cache redirects\n\nRedirects can be expensive if they are made based on data, these are cached the same as other responses - this can be disabled using skip3xx\n\n```javascript\nconst xoc = new OutputCache();\n\napp.get('/api/:channel', xoc.middleware, api.middleware, (req, res) =\u003e {    \n  res.set({'Cache-Control': 'max-age=600'});  \n  res.redirect(301, '/api/us/:channel'); //will be hit once every 600 minutes \n});\n```\n\n\n## Using an alternative cache provider - Redis\n\nOutputcache supports any cache provider by exposing its cache interface on its own 'cacheProvider' property. The only requirement is that your custom cacheProvider returns a Promise for its get method.\n\nThe example below shows how Redis can be used as the cacheProvider. \n\n```javascript\nconst xoc = require('outputcache');\nconst redis = require('redis');\nconst client = redis.createClient();\n\nconst xoc = new OutputCache({\n    cacheProvider: {\n        cache: client, //redis is now cache \n        get: key =\u003e {\n            //the standard redis module does not return a promise...\n            return new Promise(resolve =\u003e {\n                xoc.cacheProvider.cache.get(key, function (err, result) {\n                    return resolve(result);\n                });\n            });\n        },\n        set: (key, item, ttl) =\u003e {\n            xoc.cacheProvider.cache.set(key, item);\n            xoc.cacheProvider.cache.expire(key, ttl);\n        }\n    }\n});\n```\n\n## Silent failover\n\nIf there is an error with the cache provider e.g. your Redis connection or within your custom get/set, Outputcache will not bubble the error to the client using next(err) in order to remain transparent and provide failover. This allows your original route to serve a 200 if Redis fails and allows you to silently log any cache errors by listening for the 'cacheProviderError' event ([see events](#events)).\n\n\n## API\n\n### `Constructor(options)`\n\n* `options.ttl`: *(default: `600`)* the standard ttl as number in seconds for each cache item (used when `options.useCacheHeader` is false)\n* `options.maxItems`: *(default: `1000`)* the number of items allowed in the cache before older, unused items are pushed out - this can be set much higher for 'out of process' caches such as Redis\n* `options.useCacheHeader`: *(default: `true`)* use the max-age cache header from the original response as ttl by default. If you set this to false the `options.ttl` or default is used instead\n* `options.varyByQuery`: *(default: `true`)* accepts a boolean or array - true/false to use all/ignore all or array to use only specific querystring arguments in the cache key\n* `options.varyByCookies`: *(default: `[]`)* accepts an array of cookie names - the cache key will include the value of the named cookie if found in the request\n* `options.allowSkip` *(default: true)*  allow or disable forced cache misses (see below) - useful for debugging or dev time\n* `options.skip3xx`: *(default: false)* never cache 3xx responses\n* `options.skip4xx`: *(default: false)* never cache 4xx responses\n* `options.skip5xx`: *(default: false)* never cache 5xx responses\n* `options.noHeaders`: *(default: false)* do not add x-output-cache headers to the response - useful for security if you wish to hide server technologies\n* `options.staleWhileRevalidate`: *(default: 0)* the default cache provider supports the stale-while-revalidate ttl from the header or will use this setting if options.useCacheHeader is false\n* `options.caseSensitive`: *(default: true)* cache key is case-sensitive by default, this can be disabled to minimise cache keys.\n* `options.cacheProvider`: *(default: Object)*  interface for the internal cache and its get/set methods - see above example for override settings.\n\n\n**Note:** `options.varyByCookies` requires you to register a cookie parser such as the 'cookie-parser' module in your application before Outputcache.\n\n\n### Methods\n\n`.middleware(req, res, next)`\n\nThe main middleware of the module, exposes the standard req, res, next params - see examples above.\n\n---\n\n`cacheProvider.get(key)`\n\nThe get method used by the cacheProvider for returning a cache item (must return a promise).\n\n---\n\n`cacheProvider.set(key, item, ttl)`\n\nThe set method used by the cacheProvider for returning a cache item.\n\n\n### Events\n\n```javascript\nxoc.on('hit', cacheItem =\u003e //{cache hit}\n\nxoc.on('miss', info =\u003e  //{url missed}\n\nxoc.on('cacheProviderError', err =\u003e //log problem with cache engine or get/set overrides\n```\n\n\n## Logging\n\nPassing an instance of a logger to outputcache is no longer supported - hits, misses or cache errors can be logged by listening [for events](#events) on the outputcache instance. This gives the developer greater control over the log format etc.\n\n\n## HTTP Headers\n\n- Will add 'x-output-cache ms/ht {ttl} {swr}' to the response headers to indicate a miss/hit/ttl for the response and the value of the staleWhileRevalidate in cache if in use\n- Will honour all headers assigned to the original response, including for redirects\n- The x-output-cache header can be disabled by setting `options.noHeaders` to true\n- Responses with no-store, no-cache and private cache-control headers are never cached\n\n\n## Force cache skip (client-side/request bypass)\n\nIt may be useful to skip outputcache completely for specific requests, you can force a cache skip (miss) when the allowSkip option is true (default) and:\n\n- The querystring collection contains 'cache=false' value pair.\n- The request has an 'x-output-cache' header set with the value 'ms'\n- The request has an x-output-cache cookie with value 'ms'\n\nThis behaviour can be disabled by setting `options.allowSkip` to false\n\n### Status skip\n\nYou can configure outputcache to automatically skip caching responses based on your original status codes too ([skip3xx, skip4xx, skip5xx](#api)), these settings are unaffected by `options.allowSkip`\n\n\n## Performance\n\n**Note:** Always test, benchmark and gather metrics in the wild based on real user behaviour - never make assumptions.\n\nOutput caching has more impact on application performance the more it gets hit - in particular for Node, where any computational or synchronous overhead is particularly expensive. To help maximise performance:\n\n- Ensure cache keys are as simple as possible; disable querystring and cookie based caching or only allow specific querystring args to be used as keys.\n- Use [case-insensitive cache keys](#api) if your application [supports them](#troubleshooting).\n- Place outputcache as early in the request/response pipeline as possible; to minimise as much code as possible from executing, you should execute outputcache as the first middleware in your routing (after any cookie, body parsers have fired at the server level).\n- Increase your cache size; V8 only gets 1.72GB memory assigned to the process by default, ensure you set a sensible maxItems ceiling, or if you have memory available you could increase --max_old_space_size=MB.\n- Increase ttl of responses; if you can set a longer ttl, you should. In cases where some responses can be cached for a longer time than others, you should use cache-control headers to vary ttl for different responses and increase it where possible.\n\nUnder a high ratio of cache hits to misses, you will begin to see an inverse relationship between requests and latency\n\n\n![requests](https://www.dropbox.com/s/of1d38r9l3yx4km/Screen%20Shot%202017-01-13%20at%2015.26.30.png?raw=1)\n![latency](https://www.dropbox.com/s/prxts69zp1obcel/Screen%20Shot%202017-01-13%20at%2015.26.55.png?raw=1)\n\n\n## Troubleshooting\n\n- You can only cache serializable data - if you override the set or get cacheProvider methods, you should avoid stringifying or parsing the cache item - outputcache does this internally already. \n- If you are only seeing x-output-cache : 'ms' headers in the response, you might be throwing an error in your cache provider or a custom get/set method - see [silent failover](#silent-failover). \n- If your application performs redirects in routes or middleware where outputcache is used, you should place outputcache before these.  \n- `options.caseSensitive` - if you disable this option (enabled by default), ensure your application is not case-sensitive to querystring or cookie arguments, if these are enabled too.\n- `options.varyByCookies` - you must register a cookier parser before outputcache in the req/res lifecycle. This is usually done at the http server level using a module such as cookie-parser. In general, you should place outputcache after cookie and body parsers but before other middleware.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmpfdavis%2Foutputcache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmpfdavis%2Foutputcache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmpfdavis%2Foutputcache/lists"}