{"id":13580350,"url":"https://github.com/ngneat/cashew","last_synced_at":"2025-05-15T10:06:56.818Z","repository":{"id":38834830,"uuid":"230715701","full_name":"ngneat/cashew","owner":"ngneat","description":"🐿 A flexible and straightforward library that caches HTTP requests in Angular","archived":false,"fork":false,"pushed_at":"2024-01-09T07:00:41.000Z","size":3873,"stargazers_count":690,"open_issues_count":29,"forks_count":35,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-05-07T18:15:45.183Z","etag":null,"topics":["angular-http","cache","http","http-cache"],"latest_commit_sha":null,"homepage":"https://www.netbasal.com","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/ngneat.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"github":"ngneat"}},"created_at":"2019-12-29T07:05:16.000Z","updated_at":"2025-04-27T11:33:05.000Z","dependencies_parsed_at":"2024-01-10T23:01:14.362Z","dependency_job_id":"a47f967b-e341-451b-b219-c22acb226b79","html_url":"https://github.com/ngneat/cashew","commit_stats":{"total_commits":171,"total_committers":19,"mean_commits":9.0,"dds":0.6666666666666667,"last_synced_commit":"21fa8a934e5ef708a05c4effef16d91cbdeeac93"},"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngneat%2Fcashew","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngneat%2Fcashew/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngneat%2Fcashew/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngneat%2Fcashew/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ngneat","download_url":"https://codeload.github.com/ngneat/cashew/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253695656,"owners_count":21948918,"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":["angular-http","cache","http","http-cache"],"created_at":"2024-08-01T15:01:50.118Z","updated_at":"2025-05-15T10:06:56.763Z","avatar_url":"https://github.com/ngneat.png","language":"TypeScript","funding_links":["https://github.com/sponsors/ngneat"],"categories":["TypeScript","`⚡ Performance Cheatsheet`","Projects by main language","Architecture and Advanced Topics"],"sub_categories":["Other performance optimisation techniques","angular","HTTP"],"readme":"\u003cp align=\"center\"\u003e\n \u003cimg width=\"20%\" height=\"20%\" src=\"./logo.svg\"\u003e\n\u003c/p\u003e\n\n\u003e Caching is nut a problem!\n\n\u003cbr /\u003e\n\n[![@ngneat/cashew](https://github.com/ngneat/cashew/actions/workflows/ci.yml/badge.svg)](https://github.com/ngneat/cashew/actions/workflows/ci.yml)\n[![MIT](https://img.shields.io/packagist/l/doctrine/orm.svg?style=flat-square)]()\n[![coc-badge](https://img.shields.io/badge/codeof-conduct-ff69b4.svg?style=flat-square)]()\n[![commitizen](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat-square)]()\n[![PRs](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)]()\n[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)\n[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors)\n[![ngneat](https://img.shields.io/badge/@-ngneat-383636?style=flat-square\u0026labelColor=8f68d4)](https://github.com/ngneat/)\n\n## Features\n\n✅ HTTP Caching \u003cbr\u003e\n✅ State Management Mode\u003cbr\u003e\n✅ Local Storage Support \u003cbr\u003e\n✅ Handles Simultaneous Requests\u003cbr\u003e\n✅ Automatic \u0026 Manual Cache Busting \u003cbr\u003e\n✅ Hackable \u003cbr\u003e\n\nA flexible and straightforward library that caches HTTP requests in Angular\n\n## Installation\n\n```shell script\n$ npm install @ngneat/cashew\n```\n\n## Usage\n\nUse the `provideHttpCache` provider along with `withHttpCacheInterceptor` in your application providers:\n\n```ts\nimport { provideHttpCache, withHttpCacheInterceptor } from '@ngneat/cashew';\n\nbootstrapApplication(AppComponent, {\n  providers: [provideHttpClient(withInterceptors([withHttpCacheInterceptor()])), provideHttpCache()]\n});\n```\n\nAnd you're done! Now, when using Angular `HttpClient`, you can pass the `withCache` function as context, and it'll cache the response:\n\n```ts\nimport { withCache } from '@ngneat/cashew';\n\n@Injectable()\nexport class UsersService {\n  constructor(private http: HttpClient) {}\n\n  getUsers() {\n    return this.http.get('api/users', {\n      context: withCache()\n    });\n  }\n}\n```\n\nIt's as simple as that.\n\n## State Management Mode\n\nWhen working with state management like `Akita` or `ngrx`, there is no need to save the data both in the cache and in the store because the store is the single source of truth. In such a case, the only thing we want is an indication of whether the data is in the cache.\n\nWe can change the mode option to `stateManagement`:\n\n```ts\nimport { withCache } from '@ngneat/cashew';\n\n@Injectable()\nexport class UsersService {\n  constructor(private http: HttpClient) {}\n\n  getUsers() {\n    return this.http.get('api/users', {\n      context: withCache({\n        mode: 'stateManagement'\n      })\n    });\n  }\n}\n```\n\nNow instead of saving the actual response in the cache, it'll save a `boolean` and will return by default an `EMPTY` observable when the `boolean` resolves to `true`. You can change the returned source by using the `returnSource` option.\n\n## Local Storage\n\nBy default, caching is done to app memory. To switch to using local storage instead simply add:\n\n```ts\nimport { provideHttpCache, withHttpCacheInterceptor, provideHttpCacheLocalStorageStrategy } from '@ngneat/cashew';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideHttpClient(withInterceptors([withHttpCacheInterceptor()])),\n    provideHttpCache(),\n    provideHttpCacheLocalStorageStrategy()\n  ]\n});\n```\n\nTo your providers list. Note that `ttl` will also be calculated via local storage in this instance.\n\n### Versioning\n\nWhen working with `localstorage`, it's recommended to add a version:\n\n```ts\nimport { withCache } from '@ngneat/cashew';\n\n@Injectable()\nexport class UsersService {\n  constructor(private http: HttpClient) {}\n\n  getUsers() {\n    return this.http.get('api/users', {\n      context: withCache({\n        version: 'v1',\n        key: 'users'\n      })\n    });\n  }\n}\n```\n\nWhen you have a breaking change, change the version, and it'll delete the current cache automatically.\n\n## Config Options\n\nUsing the library, you might need to change the default behavior of the caching mechanism. You could do that by passing a configuration to the `provideHttpCache` function:\n\n```ts\nbootstrapApplication(AppComponent, {\n  providers: [provideHttpClient(withInterceptors([withHttpCacheInterceptor()])), provideHttpCache(config)]\n});\n```\n\nLet's go over each of the configuration options:\n\n#### `strategy`\n\nDefines the caching behavior. The library supports two different strategies:\n\n- `explicit` (default) - **only** caches API requests that explicitly use the `withCache` function\n- `implicit` - caches API requests that are of type `GET` and the response type is `JSON`. You can change this behavior by overriding the `HttpCacheGuard` provider. (See the [Hackable](#hack-the-library) section)\n\n```ts\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideHttpClient(withInterceptors([withHttpCacheInterceptor()])),\n    provideHttpCache({ strategy: 'implicit' })\n  ]\n});\n```\n\n#### `ttl`\n\nDefine the cache TTL (time to live) in milliseconds: (defaults to one hour)\n\n```ts\nbootstrapApplication(AppComponent, {\n  providers: [provideHttpClient(withInterceptors([withHttpCacheInterceptor()])), provideHttpCache({ ttl: number })]\n});\n```\n\n#### `responseSerializer`\n\nBy default, the registry returns the `original` response object. It can be dangerous if, for some reason, you mutate it. To change this behavior, you can clone the response before getting it:\n\n```ts\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideHttpClient(withInterceptors([withHttpCacheInterceptor()])),\n    provideHttpCache({\n      responseSerializer(body) {\n        return cloneDeep(body);\n      }\n    })\n  ]\n});\n```\n\n## API\n\n### WithCache\n\nCurrently, there is no way in Angular to pass `metadata` to an interceptor. The `withCache` function uses the `params` object to pass the `config` and removes it afterward in the interceptor. The function receives four optional params that are postfixed with a `$` sign so it'll not conflicts with others:\n\n- `cache` - Whether to cache the request (defaults to `true`)\n- `ttl` - TTL that will override the global\n- `key` - Custom key. (defaults to the request URL including any query params)\n- `bucket` - The [bucket](#cachebucket) in which we save the keys\n- `version` - To use when working with `localStorage` (see [Versioning](#Versioning)).\n- `clearCachePredicate(previousRequest, currentRequest)` - Return `true` to clear the cache for this key\n- `context` - Allow chaining function call that returns an `HttpContext`.\n\n```ts\nimport { requestDataChanged, withCache } from '@ngneat/cashew';\n\n@Injectable()\nexport class UsersService {\n  constructor(private http: HttpClient) {}\n\n  getUsers() {\n    return this.http.get('api/users', {\n      context: withCache({\n        withCache: false,\n        ttl: 40000,\n        key: 'users',\n        clearCachePredicate: requestDataChanged\n      })\n    });\n  }\n}\n```\n\nWhen you need to call another function that returns an `HttpContext`, you can provide the context option.\n\n```ts\nimport { withCache } from '@ngneat/cashew';\nimport { withLoadingSpinner } from '@another/library'; // \u003c-- function that returns an HttpContext\n\n@Injectable()\nexport class TodosService {\n  constructor(private http: HttpClient) {}\n\n  getTodos() {\n    return this.http.get('api/todos', {\n      context: withCache({\n        context: withLoadingSpinner()\n      })\n    });\n  }\n}\n```\n\n### CacheManager\n\nThe `CacheManager` provider, exposes an API to update and query the cache registry:\n\n- `get\u003cT\u003e(key: string): HttpResponse\u003cT\u003e` - Get the `HttpResponse` from the cache\n- `has(key: string)` - Returns a `boolean` indicates whether the provided `key` exists in the cache\n- `set(key: string, body: any, { ttl, bucket })` - Set manually a new entry in the cache\n- `delete(key: string | CacheBucket)` - Delete from the cache\n\n### CacheBucket\n\n`CacheBucket` can be useful when we need to buffer multiple requests and invalidate them at some point. For example:\n\n```ts\nimport { withCache, CacheBucket } from '@ngneat/cashew';\n\n@Injectable()\nexport class TodosService {\n  todosBucket = new CacheBucket();\n\n  constructor(\n    private http: HttpClient,\n    private manager: HttpCacheManager\n  ) {}\n\n  getTodo(id) {\n    return this.http.get(`todos/${id}`, {\n      context: withCache({\n        bucket: this.todosBucket\n      })\n    });\n  }\n\n  invalidateTodos() {\n    this.manager.delete(this.todosBucket);\n  }\n}\n```\n\nNow when we call the `invalidateTodos` method, it'll automatically delete all the `ids` that it buffered. `CacheBucket` also exposes the `add`, `has`, `delete`, and `clear` methods.\n\n## Hack the Library\n\n- `HttpCacheStorage` - The storage to use: (defaults to in-memory storage)\n\n```ts\nabstract class HttpCacheStorage {\n  abstract has(key: string): boolean;\n  abstract get(key: string): HttpResponse\u003cany\u003e;\n  abstract set(key: string, response: HttpResponse\u003cany\u003e): void;\n  abstract delete(key?: string): void;\n}\n```\n\n- `KeySerializer` - Generate the cache key based on the request: (defaults to `request.urlWithParams`)\n\n```ts\nexport abstract class KeySerializer {\n  abstract serialize(request: HttpRequest): string;\n}\n```\n\n- `HttpCacheGuard` - When using the **`implicit`** strategy it first verifies that `canActivate` is truthy:\n\n```ts\nexport abstract class HttpCacheGuard {\n  abstract canActivate(request: HttpCacheHttpRequestRequest): boolean;\n}\n```\n\nIt defaults to `request.method === 'GET' \u0026\u0026 request.responseType === 'json'`.\n\n- `TTLManager` - A class responsible for managing the requests TTL:\n\n```ts\nabstract class TTLManager {\n  abstract isValid(key: string): boolean;\n  abstract set(key: string, ttl?: number): void;\n  abstract delete(key?: string): void;\n}\n```\n\n## Compatability matrix\n\n| Cashew | Angular      |\n| ------ | ------------ |\n| ^4.0.0 | ^17.0.0      |\n| 3.1.0  | \u003e13.0.0 \u003c 17 |\n| 3.0.0  | ^13.0.0      |\n| ^2.0.0 | ^12.0.0      |\n| ^1.0.0 | ^10.0.0      |\n\n## Contributors ✨\n\nThanks go to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://www.netbasal.com\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/6745730?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eNetanel Basal\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/ngneat/cashew/commits?author=NetanelBasal\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"#design-NetanelBasal\" title=\"Design\"\u003e🎨\u003c/a\u003e \u003ca href=\"https://github.com/ngneat/cashew/commits?author=NetanelBasal\" title=\"Documentation\"\u003e📖\u003c/a\u003e \u003ca href=\"#ideas-NetanelBasal\" title=\"Ideas, Planning, \u0026 Feedback\"\u003e🤔\u003c/a\u003e \u003ca href=\"#infra-NetanelBasal\" title=\"Infrastructure (Hosting, Build-Tools, etc)\"\u003e🚇\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/itayod\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/6719615?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eItay Oded\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/ngneat/cashew/commits?author=itayod\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/shaharkazaz\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/17194830?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eShahar Kazaz\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/ngneat/cashew/commits?author=shaharkazaz\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://indepth.dev/author/layzee/\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/6364586?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eLars Gyrup Brink Nielsen\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/ngneat/cashew/commits?author=LayZeeDK\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://raisiqueira.dev\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/2914170?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eRaí Siqueira\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"#content-raisiqueira\" title=\"Content\"\u003e🖋\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/theblushingcrow\"\u003e\u003cimg src=\"https://avatars3.githubusercontent.com/u/638818?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eInbal Sinai\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/ngneat/cashew/commits?author=theblushingcrow\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"https://github.com/ngneat/cashew/commits?author=theblushingcrow\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://binary.com.au\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/175909?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eJames Manners\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/ngneat/cashew/commits?author=jmannau\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/mokipedia\"\u003e\u003cimg src=\"https://avatars3.githubusercontent.com/u/11502273?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003emokipedia\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/ngneat/cashew/commits?author=mokipedia\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"https://github.com/ngneat/cashew/commits?author=mokipedia\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-enable --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fngneat%2Fcashew","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fngneat%2Fcashew","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fngneat%2Fcashew/lists"}