{"id":15655496,"url":"https://github.com/ecyrbe/stalier","last_synced_at":"2025-04-14T03:34:00.235Z","repository":{"id":41435017,"uuid":"509558474","full_name":"ecyrbe/stalier","owner":"ecyrbe","description":"Stalier is a stale-while-revalidate middleware for your backend","archived":false,"fork":false,"pushed_at":"2025-04-11T14:34:10.000Z","size":857,"stargazers_count":25,"open_issues_count":9,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-11T15:48:46.847Z","etag":null,"topics":["cache","express","nestjs","stale-while-revalidate"],"latest_commit_sha":null,"homepage":"","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/ecyrbe.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-07-01T18:47:52.000Z","updated_at":"2024-07-15T15:21:11.000Z","dependencies_parsed_at":"2023-12-18T01:37:43.105Z","dependency_job_id":"518b095b-4587-4fb5-a4e3-8ec0d02601fd","html_url":"https://github.com/ecyrbe/stalier","commit_stats":{"total_commits":166,"total_committers":2,"mean_commits":83.0,"dds":"0.22289156626506024","last_synced_commit":"579d88feb35245df4a9ef97fbac7d286c8d489b7"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecyrbe%2Fstalier","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecyrbe%2Fstalier/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecyrbe%2Fstalier/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecyrbe%2Fstalier/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ecyrbe","download_url":"https://codeload.github.com/ecyrbe/stalier/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248815589,"owners_count":21165952,"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","express","nestjs","stale-while-revalidate"],"created_at":"2024-10-03T12:59:30.011Z","updated_at":"2025-04-14T03:34:00.211Z","avatar_url":"https://github.com/ecyrbe.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":" \u003cp align=\"center\"\u003e\n   \u003ca href=\"https://github.com/ecyrbe/stalier\"\u003e\n     \u003cimg align=\"center\" src=\"https://raw.githubusercontent.com/ecyrbe/stalier/main/docs/logo.png\" width=\"256px\" alt=\"Stalier logo\"\u003e\n   \u003c/a\u003e\n \u003c/p\u003e\n \u003cp align=\"center\"\u003e\n    Stalier is a stale-while-revalidate middleware for your backend\n \u003c/p\u003e\n \n \u003cp align=\"center\"\u003e\n   \u003ca href=\"https://www.npmjs.com/package/stalier\"\u003e\n   \u003cimg src=\"https://img.shields.io/npm/v/stalier.svg\" alt=\"langue typescript\"\u003e\n   \u003c/a\u003e\n   \u003ca href=\"https://www.npmjs.com/package/stalier\"\u003e\n   \u003cimg alt=\"npm\" src=\"https://img.shields.io/npm/dw/stalier\"\u003e\n   \u003c/a\u003e\n   \u003ca href=\"https://github.com/ecyrbe/stalier/blob/main/LICENSE\"\u003e\n    \u003cimg alt=\"GitHub\" src=\"https://img.shields.io/github/license/ecyrbe/stalier\"\u003e   \n   \u003c/a\u003e\n   \u003cimg alt=\"GitHub Workflow Status\" src=\"https://img.shields.io/github/workflow/status/ecyrbe/stalier/CI\"\u003e\n \u003c/p\u003e\n\nStalier is cache strategy middleware controled by your frontend by using `x-stalier-cache-control` header.  \nThis means that instead of your backend sending `Cache-Control` header to your browser for your browser to cache the returned data, your frontend will send the header `X-Stalier-Cache-Control` to your backend for it to cache the returned data from your source of truth.\n  \n- It is an advanced middleware with support for stale-while-revalidate strategy.  \n- Stalier will act as a proxy that caches the response if it sees a `X-Stalier-Cache-Control` header.  \n- Since it's embedded in your backend, it's much more efficient than using a separate proxy.  \n- It implements part of RFC7234 and RFC5861 but on the backend. It does not use `cache-control` since the cache is controlled by the frontend.\n- If you want both your browser and backend to cache the responses, you can use `x-stalier-cache-control` for requests and `cache-control` for responses at the same time.\n  \n**Table of contents**  \n- [Install](#install)\n- [Usage : backend](#usage--backend)\n  - [Express](#express)\n    - [Stalier Options](#stalier-options)\n    - [Handle per user caching](#handle-per-user-caching)\n  - [NestJS](#nestjs)\n    - [Create a Cache instance for StalierInterceptor](#create-a-cache-instance-for-stalierinterceptor)\n    - [Options for StalierModule](#options-for-staliermodule)\n    - [StalierInterceptor](#stalierinterceptor)\n- [Usage : frontend](#usage--frontend)\n  - [Example](#example)\n\n## Install\n\n```bash\nnpm install stalier\n```\n\nor\n\n```bash\nyarn add stalier\n```\n## Usage : backend\n\n### Express\n\nStalier do not provide a cache on it's own, nor does it provide a logger. But you provide them both in the constructor.\n\n```js\nimport express from 'express';\nimport cacheManager from 'cache-manager';\nimport redisStore from 'cache-manager-ioredis';\n\nimport { stalier } from 'stalier';\n\nconst redisCache = cacheManager.caching({ store: redisStore });\n\n// Create a new express app\nconst app = express();\n// add stalier middleware\napp.use(stalier({ appName: 'test', cacheClient: redisCache }));\n```\n\n#### Stalier Options\n\n```typescript\ntype StalierMiddlewareOptions = {\n  /**\n   * name of the upstream application\n   */\n  appName: string;\n  /**\n   * client to use for caching\n   * should have an async `get` method and `set` method\n   */\n  cacheClient: CacheClient;\n  /**\n   * function to generate a cache key per request\n   * Use a custom one to handle per user caching\n   * @default `\u003cappName\u003e-\u003cHTTP Verb\u003e-\u003cpath\u003e`\n   */\n  cacheKeyGen?: (req: Request) =\u003e string;\n  /**\n   * logger to use for logging\n   * should have a log, warn and error method that takes a message parameter\n   * @default `console`\n   */\n  logger?: Logger;\n};\n```\n\n#### Handle per user caching\n\n```js\nimport express from 'express';\nimport cacheManager from 'cache-manager';\nimport redisStore from 'cache-manager-ioredis';\nimport jsonwebtoken from 'jsonwebtoken';\nimport { stalier } from 'stalier';\n\n// your user middleware\nimport { userMiddleware } from './userMiddleware';\n\nconst redisCache = cacheManager.caching({ store: redisStore });\n\nconst appName = 'test';\n// cache key per user request that extracts the user email from your user middleware\nconst cacheKeyGen = (req: Request) =\u003e {\n  if(req.user) {\n    return `${appName}-${req.method}-${req.path}-${req.user.id}`;\n  }\n  return `${appName}-${req.method}-${req.path}`;\n}\n\napp.use(userMiddleware);\napp.use(stalier({\n  appName,\n  cacheClient: redisCache,\n  cacheKeyGen,\n}));\n```\n\n### NestJS\n\nStalier uses the `NestJsInterceptor` to intercept the request and response, and uses the Cache Module to cache the response.\n\n#### Create a Cache instance for StalierInterceptor\n\nStalier module allows you to instanciate a Cache with the `cache-manager` library. The cacheOptions can take a single option or an array of options to use multicache strategy from `cache-manager`.\nStalier module as two ways of instanciating a cache:\n- `forRoot` - with options known at compile time\n- `forRootAsync` - with options known at runtime, usually loaded from a config service\n  \nAs the name suggests, your should only load Stalier module once in your application.\n  \n```typescript\nimport { Controller, Get } from '@nestjs/common';\nimport { UseStalierInterceptor, StalierModule, UseCacheKeyGen } from 'stalier';\n\nimport { ConfigModule, ConfigService } from '@nestjs/config';\n\n@Module({\n  imports: [\n    ConfigModule.forRoot({ isGlobal: true }),\n    StalierModule.forRootAsync({\n      useFactory: (configService: ConfigService) =\u003e ({\n        appName: 'test',\n        cacheOptions: configService.get('CACHE_OPTIONS'),\n        isGlobal: true,\n      }),\n      inject: [ConfigService],\n    }),\n  ],\n  controllers: [MyAppController],\n})\n```\n\n#### Options for StalierModule\n\n```typescript\n\ninterface StalierModuleOptions {\n  /**\n   * name of the app - used to generate cache key\n   */\n  appName: string;\n  /**\n   * function to generate cache key from a request\n   */\n  cacheKeyGen?: KeyGenFn;\n  /**\n   * options for cache-manager\n   */\n  cacheOptions: StalierCacheManagerOptions | StalierCacheManagerOptions[];\n  /**\n   * if true, stalier cache will be global and shared across all modules\n   */\n  isGlobal?: boolean;\n}\ninterface StalierCacheManagerOptions {\n  /**\n   * cache-manager store to use\n   */\n  store: 'memory' | 'none' | CacheStore | CacheStoreFactory;\n  /**\n   * maximum number of items to store in the cache - only for memory cache\n   */\n  max?: number;\n  /**\n   * time to live in seconds - if not set no ttl is set by default\n   */\n  ttl?: number;\n}\n```\n\n#### StalierInterceptor\n\nYou can load StalierInterceptor Globally (see useGlobalInterceptors) or per Controller (see useInterceptors).\n\n```typescript\n@UseStalierInterceptor()\n@Controller()\nclass MyAppController {\n  @Get('/default-key')\n  getDefaultKey() {\n    return { hello: 'world' };\n  }\n\n  // per user caching\n  @CacheKeyUser((req) =\u003e req.user.id)\n  @Get('/custom-key')\n  getCustomKey() {\n    return { hello: 'world' };\n  }\n}\n```\n\nBy default, stalier interceptor will use the request path as the cache key.  \nTo handle per user caching, you can use the `CacheKeyUser` decorator. You can apply it per controller or per method.  \nTo apply a static key, use the `CacheKey` decorator. Or to have fine grained control, you can use the `CacheKeyGen` decorator.\n## Usage : frontend\n\nStalier is using the `x-stalier-cache-control` header to control the cache behaviour within your frontend.\nit supports the following params:\n\n- s-maxage: time in seconds indicating the maximum time the response should be cached. A value of 0 means no caching.\n- stale-while-revalidate: time in seconds indicating the time the response should be cached while revalidating. A value of 0 means no window for revalidation and only use cached content.\n\n### Example\n\nIf you want a content to be cached for 10 seconds and have a revalidation window of 50 seconds, you can use the following headers:\n\n```http\nGET /content HTTP/1.1\nx-stalier-cache-control: s-maxage=10, stale-while-revalidate=50\n```\n\nif it's not present, it will be cached for 10 seconds, and get back the fresh content with the following headers:\n\n```http\nHTTP/1.1 200 OK\nx-cache-status: MISS\n```\n\nRequesting another time the same content within 10 seconds will return the cached content with the following headers:\n\n```http\nHTTP/1.1 200 OK\nx-cache-status: HIT\n```\n\nRequesting another time the same content within 50 seconds will return the cached content and try to refresh the cache in the background and return following headers:\n\n```http\nHTTP/1.1 200 OK\nx-cache-status: STALE\n```\n\nAnother call will then return the refreshed content with the following headers:\n\n```http\nHTTP/1.1 200 OK\nx-cache-status: HIT\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecyrbe%2Fstalier","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fecyrbe%2Fstalier","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecyrbe%2Fstalier/lists"}