{"id":15630711,"url":"https://github.com/kikobeats/cacheable-response","last_synced_at":"2025-04-04T10:05:15.127Z","repository":{"id":34220444,"uuid":"171842101","full_name":"Kikobeats/cacheable-response","owner":"Kikobeats","description":"An HTTP compliant route path middleware for serving cache response with invalidation support.","archived":false,"fork":false,"pushed_at":"2024-06-27T16:45:58.000Z","size":330,"stargazers_count":218,"open_issues_count":0,"forks_count":24,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-04T09:43:07.751Z","etag":null,"topics":["cache","cache-control","http","server-rendering"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/Kikobeats.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-02-21T09:31:44.000Z","updated_at":"2024-12-18T12:00:27.000Z","dependencies_parsed_at":"2023-02-18T17:50:21.455Z","dependency_job_id":"d06d69a4-a0f3-4490-81dd-8e7567774021","html_url":"https://github.com/Kikobeats/cacheable-response","commit_stats":{"total_commits":267,"total_committers":13,"mean_commits":20.53846153846154,"dds":"0.22097378277153557","last_synced_commit":"9d265f54e0f787bd52cf81bce6caffa8d234b0c2"},"previous_names":[],"tags_count":112,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kikobeats%2Fcacheable-response","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kikobeats%2Fcacheable-response/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kikobeats%2Fcacheable-response/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kikobeats%2Fcacheable-response/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Kikobeats","download_url":"https://codeload.github.com/Kikobeats/cacheable-response/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247157281,"owners_count":20893220,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["cache","cache-control","http","server-rendering"],"created_at":"2024-10-03T10:35:39.406Z","updated_at":"2025-04-04T10:05:15.079Z","avatar_url":"https://github.com/Kikobeats.png","language":"JavaScript","readme":"# cacheable-response\n\n![Last version](https://img.shields.io/github/tag/Kikobeats/cacheable-response.svg?style=flat-square)\n[![Coverage Status](https://img.shields.io/coveralls/Kikobeats/cacheable-response.svg?style=flat-square)](https://coveralls.io/github/Kikobeats/cacheable-response)\n[![NPM Status](https://img.shields.io/npm/dm/cacheable-response.svg?style=flat-square)](https://www.npmjs.org/package/cacheable-response)\n\n\u003e An HTTP compliant route path middleware for serving cache response with invalidation support.\n\n## Why\n\nServer Side Rendering (_SSR_) is a luxurious but necessary thing if you want to have a first class user experience.\n\nThe main issue of doing server-side things is the extra cost associated with dynamic things: The server will take CPU cycles to compute the value to be served, probably discarded in the next page reload, losing precious resources in the process.\n\nInstead of serving a real time™ – and costly – response, we can say it is OK serving a pre-calculated response but much much cheaper.\n\nThat will save CPU cycles, saving them for things that really matters.\n\n## Caching states\n\n| Value    | Description                                                                                                       |\n|----------|-------------------------------------------------------------------------------------------------------------------|\n| `MISS`   | The resource was looked into the cache but did not find it, so a new copy is generated and placed into the cache. |\n| `HIT`    | The resources was found into the cache, being generated by a previous access.                                     |\n| `EXPIRED`| The resouce was found but it is expired, being necessary regerate it.                                             |\n| `BYPASS` | The cache is forced to be bypassed, regenerating the resource.                                                    |\n| `STALE`  | The resource is expired but it's served while a new cache copy is generated in background.                        |\n\n## Install\n\n```bash\n$ npm install cacheable-response --save\n```\n\n## Get Started\n\n**cacheable-response** is a HTTP middleware for a serving pre-calculated response.\n\nIt's like a LRU cache but with all the logic necessary for auto-invalidate response copies and refresh them.\n\nImagine you are currently running an HTTP microservice to compute something heavy in terms of CPU\n\n```js\nconst server = ({ req, res }) =\u003e {\n  const data = doSomething(req)\n  res.send(data)\n}\n```\n\nTo leverage caching capabilities, just you need to adapt your HTTP based project a bit for following **cacheable-response** interface\n\n```js\nconst cacheableResponse = require('cacheable-response')\n\nconst ssrCache = cacheableResponse({\n  get: ({ req, res }) =\u003e ({\n    data: doSomething(req),\n    ttl: 86400000 // 24 hours\n  }),\n  send: ({ data, res, req }) =\u003e res.send(data)\n})\n```\n\nAt least, **cacheable-response** needs two things:\n\n- **get**: It creates a fresh cacheable response associated with the current route path.\n- **send**: It determines how the response should be rendered.\n\n**cacheable-response** is _framework agnostic_: It could be used with any library that accepts `(request, response)` as input.\n\n```js\nconst http = require('http')\n/* Explicitly pass `cacheable-response` as server */\nhttp\n  .createServer((req, res) =\u003e ssrCache({ req, res }))\n  .listen(3000)\n```\n\nIt could be use in the express way too:\n\n```js\nconst express = require('express')\nconst app = express()\n\n/* Passing `cacheable-response` instance as middleware */\napp\n  .use((req, res) =\u003e ssrCache({ req, res }))\n```\n\nSee more [examples](#examples).\n\nAt all times the cache status is reflected as `x-cache` headers in the response.\n\nThe first resource access will be a `MISS`.\n\n```bash\nHTTP/2 200\ncache-control: public, max-age=7200, stale-while-revalidate=300\nETag: \"d-pedE0BZFQNM7HX6mFsKPL6l+dUo\"\nx-cache-status: MISS\nx-cache-expired-at: 1h 59m 60s\n```\n\nSuccessive resource access under the `ttl` period returns a `HIT`\n\n```bash\nHTTP/2 200\ncache-control: public, max-age=7170, stale-while-revalidate=298\nETag: \"d-pedE0BZFQNM7HX6mFsKPL6l+dUo\"\nx-cache-status: HIT\nx-cache-expired-at: 1h 59m 30s\n```\n\nAfter `ttl` period expired, the cache will be invalidated and refreshed in the next request.\n\nIn case you need you can force invalidate a cache response passing `force=true` as part of your query parameters.\n\n```bash\ncurl https://myserver.dev/user # MISS (first access)\ncurl https://myserver.dev/user # HIT (served from cache)\ncurl https://myserver.dev/user # HIT (served from cache)\ncurl https://myserver.dev/user?force=true # BYPASS (skip cache copy)\n```\n\nIn that case, the `x-cache-status` will reflect a `'BYPASS'` value.\n\nAdditionally, you can configure a stale ttl:\n\n```js\nconst cacheableResponse = require('cacheable-response')\n\nconst ssrCache = cacheableResponse({\n  get: ({ req, res }) =\u003e ({\n    data: doSomething(req),\n    ttl: 86400000, // 24 hours\n    staleTtl: 3600000 // 1h\n  }),\n  send: ({ data, res, req }) =\u003e res.send(data)\n})\n```\n\nThe stale ttl maximizes your cache HITs, allowing you to serve a no fresh cache copy _while_ doing revalidation on the background. \n\n```bash\ncurl https://myserver.dev/user # MISS (first access)\ncurl https://myserver.dev/user # HIT (served from cache)\ncurl https://myserver.dev/user # STALE (23 hours later, background revalidation)\ncurl https://myserver.dev/user?force=true # HIT (fresh cache copy for the next 24 hours)\n```\n\nThe library provides enough good sensible defaults for most common scenarios and you can tune these values based on your use case.\n\n## API\n\n### cacheableResponse([options])\n\n#### options\n\n##### bypassQueryParameter\n\nType: `string`\u003cbr/\u003e\nDefault: `'force'`\n\nThe name of the query parameter to be used for skipping the cache copy in an intentional way.\n\n##### cache\n\nType: `boolean`\u003cbr/\u003e\nDefault: `new Keyv({ namespace: 'ssr' })`\n\nThe cache instance used for backed your pre-calculated server side response copies.\n\nThe library delegates in [keyv](https://keyvhq.js.org/), a tiny key value store with [multi adapter support](https://keyvhq.js.org/#/?id=storage-adapters).\n\nIf you don't specify it, a memory cache will be used.\n\n##### compress\n\nType: `boolean`\u003cbr/\u003e\nDefault: `false`\n\nEnable compress/decompress data using brotli compression format.\n\n##### get\n\n_Required_\u003cbr/\u003e\nType: `function`\u003cbr/\u003e\n\nThe method to be called for creating a fresh cacheable response associated with the current route path.\n\n```js\nasync function get ({ req, res }) {\n  const data = doSomething(req, res)\n  const ttl = 86400000 // 24 hours\n  const headers = { userAgent: 'cacheable-response' }\n  return { data, ttl, headers }\n}\n```\n\nThe method will received `({ req, res })` and it should be returns:\n\n- **data** `object`|`string`: The content to be saved on the cache.\n- **ttl** `number`: The quantity of time in milliseconds the content is considered valid on the cache. Don't specify it means use default [`ttl`](#ttl).\n- **createdAt** `date`: The timestamp associated with the content (`Date.now()` by default).\n\nAny other property can be specified and will passed to `.send`.\n\nIn case you want to bypass the cache, preventing caching a value (e.g., when an error occurred), you should return `undefined` or `null`.\n\n##### key\n\nType: `function`\u003cbr/\u003e\nDefault: `({ req }) =\u003e req.url)`\n\nIt specifies how to compute the cache key, taking `req, res` as input.\n\nAlternatively, it can return an array:\n\n```js\nconst key = ({ req }) =\u003e [getKey({ req }), req.query.force]\n```\n\nwhere the second parameter represents whether to force the cache entry to expire.\n\n##### logger\n\nType: `function`\u003cbr/\u003e\nDefault: `() =\u003e {}`\n\nWhen it's present, every time cacheable-response is called, a log will be printed.\n\n##### send\n\n_Required_\u003cbr/\u003e\nType: `function`\u003cbr/\u003e\n\nThe method used to determinate how the content should be rendered.\n\n```js\nasync function send ({ req, res, data, headers }) {\n  res.setHeader('user-agent', headers.userAgent)\n  res.send(data)\n}\n```\n\nIt will receive `({ req, res, data, ...props })` being `props` any other data supplied to `.get`.\n\n##### staleTtl\n\nType: `number`|`boolean`|`function`\u003cbr/\u003e\nDefault: `3600000`\n\nNumber of milliseconds that indicates grace period after response cache expiration for refreshing it in the background. The latency of the refresh is hidden from the user.\n\nThis value can be specified as well providing it as part of [`.get`](#get) output.\n\nThe value will be associated with [`stale-while-revalidate`](https://www.mnot.net/blog/2014/06/01/chrome_and_stale-while-revalidate) directive.\n\nYou can pass a `false` to disable it.\n\n##### ttl\n\nType: `number`|`function`\u003cbr/\u003e\nDefault: `86400000`\n\nNumber of milliseconds a cache response is considered valid.\n\nAfter this period of time, the cache response should be refreshed.\n\nThis value can be specified as well providing it as part of [`.get`](#get) output.\n\nIf you don't provide one, this be used as fallback for avoid keep things into cache forever.\n\n##### serialize\n\nType: `function`\u003cbr/\u003e\nDefault: `JSON.stringify`\n\nSet the serializer method to be used before compress.\n\n##### deserialize\n\nType: `function`\u003cbr/\u003e\nDefault: `JSON.parse`\n\nSet the deserialize method to be used after decompress.\n\n## Pro-tip: Distributed cache with CloudFlare™️\n\n\u003e This content is not sponsored; Just I consider CloudFlare is doing a good job offering a cache layer as part of their free tier.\n\nImagine what could be better than having one cache layer? Exactly, two cache layers.\n\nIf your server domain is connected with CloudFlare you can take advantage of [unlimited bandwidth usage](https://web.archive.org/web/20200428000736/https://support.cloudflare.com/hc/en-us/articles/205177068-How-does-Cloudflare-work-).\n\n![](https://i.imgur.com/2BCHVzh.png)\n\nFor doing that, you need to setup a `Page Rule` over your domain specifing you want to enable cache. [Read more how to do that](https://web.archive.org/web/20190429045303/https://support.cloudflare.com/hc/en-us/articles/115000150272-How-do-I-use-Cache-Everything-with-Cloudflare-).\n\nNext time you query about a resource, a new `cf-cache-status` appeared as part of your headers response.\n\n```bash\nHTTP/2 200\ncache-control: public, max-age=7200, stale-while-revalidate=300\nETag: \"d-pedE0BZFQNM7HX6mFsKPL6l+dUo\"\nx-cache-status: MISS\nx-cache-expired-at: 1h 59m 60s\ncf-cache-status: MISS\n```\n\nCloudFlare will [respect your `cache-control` policy](https://web.archive.org/web/20190323033009/https://support.cloudflare.com/hc/en-us/articles/202775670-How-Do-I-Tell-Cloudflare-What-to-Cache-), creating another caching layer reflected by `cf-cache-status`\n\n```bash\nHTTP/2 200\ncache-control: public, max-age=7200, stale-while-revalidate=300\nETag: \"d-pedE0BZFQNM7HX6mFsKPL6l+dUo\"\nx-cache-status: MISS\nx-cache-expired-at: 1h 59m 60s\ncf-cache-status: HIT\n```\n\nNote how in this second request `x-cache-status` is still a `MISS`.\n\nThat's because CloudFlare way for caching the content includes caching the response headers.\n\nThe headers associated with the cache copy will the headers from the first request. You need to look at `cf-cache-status` instead.\n\nYou can have a better overview of the percentage of success by looking your CloudFlare domain analytics\n\n![](https://i.imgur.com/WnPnRlk.png)\n\n## Examples\n\n\u003e Make a PR for adding your project!\n\n- [`unavatar.io`](https://unavatar.io)\n- [`html-microservice`](https://github.com/microlinkhq/html/blob/master/src/index.js#L9)\n\n## Bibliography\n\n- [Server rendered pages are not optional, by Guillermo Rauch](https://rauchg.com/2014/7-principles-of-rich-web-applications#server-rendered-pages-are-not-optional).\n- [Increasing the Performance of Dynamic Next.JS Websites, by scale AI](https://scale.ai/blog/performance-on-next-js-websites).\n- [The Benefits of Microcaching, by NGINX](https://www.nginx.com/blog/benefits-of-microcaching-nginx/).\n- [Cache-Control for Civilians, by Harry Robert](https://csswizardry.com/2019/03/cache-control-for-civilians/)\n- [Demystifying HTTP Caching, by Bharathvaj Ganesan](https://codeburst.io/demystifying-http-caching-7457c1e4eded).\n- [Keeping things fresh with stale-while-revalidate, by Jeff Posnick](https://web.dev/stale-while-revalidate/).\n\n## License\n\n**cacheable-response** © [Kiko Beats](https://kikobeats.com), released under the [MIT](https://github.com/Kikobeats/cacheable-response/blob/master/LICENSE.md) License.\u003cbr/\u003e\nAuthored and maintained by Kiko Beats with help from [contributors](https://github.com/Kikobeats/cacheable-response/contributors).\n\n\u003e [kikobeats.com](https://kikobeats.com) · GitHub [Kiko Beats](https://github.com/Kikobeats) · Twitter [@Kikobeats](https://twitter.com/Kikobeats)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkikobeats%2Fcacheable-response","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkikobeats%2Fcacheable-response","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkikobeats%2Fcacheable-response/lists"}