UNPKG

12.7 kBMarkdownView Raw
1# cacheable-request
2
3> Wrap native HTTP requests with RFC compliant cache support
4
5[![tests](https://github.com/jaredwray/cacheable-request/actions/workflows/tests.yaml/badge.svg)](https://github.com/jaredwray/cacheable-request/actions/workflows/tests.yaml)
6[![codecov](https://codecov.io/gh/jaredwray/cacheable-request/branch/master/graph/badge.svg?token=LDLaqe4PsI)](https://codecov.io/gh/jaredwray/cacheable-request)
7[![npm](https://img.shields.io/npm/dm/cacheable-request.svg)](https://www.npmjs.com/package/cacheable-request)
8[![npm](https://img.shields.io/npm/v/cacheable-request.svg)](https://www.npmjs.com/package/cacheable-request)
9
10[RFC 7234](http://httpwg.org/specs/rfc7234.html) compliant HTTP caching for native Node.js HTTP/HTTPS requests. Caching works out of the box in memory or is easily pluggable with a wide range of storage adapters.
11
12**Note:** This is a low level wrapper around the core HTTP modules, it's not a high level request library.
13
14# Table of Contents
15* [Latest Changes](#latest-changes)
16* [Features](#features)
17* [Install and Usage](#install-and-usage)
18* [Storage Adapters](#storage-adapters)
19* [API](#api)
20* [Using Hooks](#using-hooks)
21* [Contributing](#contributing)
22* [Ask a Question](#ask-a-question)
23* [License](#license) (MIT)
24
25# Latest Changes
26
27## Breaking Changes with v10.0.0
28This release contains breaking changes. This is the new way to use this package.
29
30### Usage Before v10
31```js
32import http from 'http';
33import CacheableRequest from 'cacheable-request';
34
35// Then instead of
36const req = http.request('http://example.com', cb);
37req.end();
38
39// You can do
40const cacheableRequest = new CacheableRequest(http.request);
41const cacheReq = cacheableRequest('http://example.com', cb);
42cacheReq.on('request', req => req.end());
43// Future requests to 'example.com' will be returned from cache if still valid
44
45// You pass in any other http.request API compatible method to be wrapped with cache support:
46const cacheableRequest = new CacheableRequest(https.request);
47const cacheableRequest = new CacheableRequest(electron.net);
48```
49
50### Usage After v10.1.0
51```js
52
53```js
54import CacheableRequest from 'cacheable-request';
55
56// Now You can do
57const cacheableRequest = new CacheableRequest(http.request).request();
58const cacheReq = cacheableRequest('http://example.com', cb);
59cacheReq.on('request', req => req.end());
60// Future requests to 'example.com' will be returned from cache if still valid
61
62// You pass in any other http.request API compatible method to be wrapped with cache support:
63const cacheableRequest = new CacheableRequest(https.request).request();
64const cacheableRequest = new CacheableRequest(electron.net).request();
65```
66
67The biggest change is that when you do a `new` CacheableRequest you now want to call `request` method will give you the instance to use.
68
69```diff
70- const cacheableRequest = new CacheableRequest(http.request);
71+ const cacheableRequest = new CacheableRequest(http.request).request();
72```
73
74### ESM Support in version 9 and higher.
75
76We are now using pure esm support in our package. If you need to use commonjs you can use v8 or lower. To learn more about using ESM please read this from `sindresorhus`: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
77
78## Features
79
80- Only stores cacheable responses as defined by RFC 7234
81- Fresh cache entries are served directly from cache
82- Stale cache entries are revalidated with `If-None-Match`/`If-Modified-Since` headers
83- 304 responses from revalidation requests use cached body
84- Updates `Age` header on cached responses
85- Can completely bypass cache on a per request basis
86- In memory cache by default
87- Official support for Redis, Memcache, Etcd, MongoDB, SQLite, PostgreSQL and MySQL storage adapters
88- Easily plug in your own or third-party storage adapters
89- If DB connection fails, cache is automatically bypassed ([disabled by default](#optsautomaticfailover))
90- Adds cache support to any existing HTTP code with minimal changes
91- Uses [http-cache-semantics](https://github.com/pornel/http-cache-semantics) internally for HTTP RFC 7234 compliance
92
93## Install and Usage
94
95```shell
96npm install cacheable-request
97```
98
99```js
100import http from 'http';
101import CacheableRequest from 'cacheable-request';
102
103// Then instead of
104const req = http.request('http://example.com', cb);
105req.end();
106
107// You can do
108const cacheableRequest = new CacheableRequest(http.request).createCacheableRequest();
109const cacheReq = cacheableRequest('http://example.com', cb);
110cacheReq.on('request', req => req.end());
111// Future requests to 'example.com' will be returned from cache if still valid
112
113// You pass in any other http.request API compatible method to be wrapped with cache support:
114const cacheableRequest = new CacheableRequest(https.request).createCacheableRequest();
115const cacheableRequest = new CacheableRequest(electron.net).createCacheableRequest();
116```
117
118## Storage Adapters
119
120`cacheable-request` uses [Keyv](https://github.com/jaredwray/keyv) to support a wide range of storage adapters.
121
122For example, to use Redis as a cache backend, you just need to install the official Redis Keyv storage adapter:
123
124```
125npm install @keyv/redis
126```
127
128And then you can pass `CacheableRequest` your connection string:
129
130```js
131const cacheableRequest = new CacheableRequest(http.request, 'redis://user:pass@localhost:6379').createCacheableRequest();
132```
133
134[View all official Keyv storage adapters.](https://github.com/jaredwray/keyv#official-storage-adapters)
135
136Keyv also supports anything that follows the Map API so it's easy to write your own storage adapter or use a third-party solution.
137
138e.g The following are all valid storage adapters
139
140```js
141const storageAdapter = new Map();
142// or
143const storageAdapter = require('./my-storage-adapter');
144// or
145const QuickLRU = require('quick-lru');
146const storageAdapter = new QuickLRU({ maxSize: 1000 });
147
148const cacheableRequest = new CacheableRequest(http.request, storageAdapter).createCacheableRequest();
149```
150
151View the [Keyv docs](https://github.com/jaredwray/keyv) for more information on how to use storage adapters.
152
153## API
154
155### new cacheableRequest(request, [storageAdapter])
156
157Returns the provided request function wrapped with cache support.
158
159#### request
160
161Type: `function`
162
163Request function to wrap with cache support. Should be [`http.request`](https://nodejs.org/api/http.html#http_http_request_options_callback) or a similar API compatible request function.
164
165#### storageAdapter
166
167Type: `Keyv storage adapter`<br>
168Default: `new Map()`
169
170A [Keyv](https://github.com/jaredwray/keyv) storage adapter instance, or connection string if using with an official Keyv storage adapter.
171
172### Instance
173
174#### cacheableRequest(opts, [cb])
175
176Returns an event emitter.
177
178##### opts
179
180Type: `object`, `string`
181
182- Any of the default request functions options.
183- Any [`http-cache-semantics`](https://github.com/kornelski/http-cache-semantics#constructor-options) options.
184- Any of the following:
185
186###### opts.cache
187
188Type: `boolean`<br>
189Default: `true`
190
191If the cache should be used. Setting this to false will completely bypass the cache for the current request.
192
193###### opts.strictTtl
194
195Type: `boolean`<br>
196Default: `false`
197
198If set to `true` once a cached resource has expired it is deleted and will have to be re-requested.
199
200If set to `false` (default), after a cached resource's TTL expires it is kept in the cache and will be revalidated on the next request with `If-None-Match`/`If-Modified-Since` headers.
201
202###### opts.maxTtl
203
204Type: `number`<br>
205Default: `undefined`
206
207Limits TTL. The `number` represents milliseconds.
208
209###### opts.automaticFailover
210
211Type: `boolean`<br>
212Default: `false`
213
214When set to `true`, if the DB connection fails we will automatically fallback to a network request. DB errors will still be emitted to notify you of the problem even though the request callback may succeed.
215
216###### opts.forceRefresh
217
218Type: `boolean`<br>
219Default: `false`
220
221Forces refreshing the cache. If the response could be retrieved from the cache, it will perform a new request and override the cache instead.
222
223##### cb
224
225Type: `function`
226
227The callback function which will receive the response as an argument.
228
229The response can be either a [Node.js HTTP response stream](https://nodejs.org/api/http.html#http_class_http_incomingmessage) or a [responselike object](https://github.com/lukechilds/responselike). The response will also have a `fromCache` property set with a boolean value.
230
231##### .on('request', request)
232
233`request` event to get the request object of the request.
234
235**Note:** This event will only fire if an HTTP request is actually made, not when a response is retrieved from cache. However, you should always handle the `request` event to end the request and handle any potential request errors.
236
237##### .on('response', response)
238
239`response` event to get the response object from the HTTP request or cache.
240
241##### .on('error', error)
242
243`error` event emitted in case of an error with the cache.
244
245Errors emitted here will be an instance of `CacheableRequest.RequestError` or `CacheableRequest.CacheError`. You will only ever receive a `RequestError` if the request function throws (normally caused by invalid user input). Normal request errors should be handled inside the `request` event.
246
247To properly handle all error scenarios you should use the following pattern:
248
249```js
250cacheableRequest('example.com', cb)
251 .on('error', err => {
252 if (err instanceof CacheableRequest.CacheError) {
253 handleCacheError(err); // Cache error
254 } else if (err instanceof CacheableRequest.RequestError) {
255 handleRequestError(err); // Request function thrown
256 }
257 })
258 .on('request', req => {
259 req.on('error', handleRequestError); // Request error emitted
260 req.end();
261 });
262```
263**Note:** Database connection errors are emitted here, however `cacheable-request` will attempt to re-request the resource and bypass the cache on a connection error. Therefore a database connection error doesn't necessarily mean the request won't be fulfilled.
264
265
266## Using Hooks
267Hooks have been implemented since version `v9` and are very useful to modify response before saving it in cache. You can use hooks to modify response before saving it in cache. You can also use hooks to modify response before returning it to user.
268
269### Add Hooks
270
271The hook will pre compute response right before saving it in cache. You can include many hooks and it will run in order you add hook on response object.
272
273```js
274import http from 'http';
275import CacheableRequest from 'cacheable-request';
276
277const cacheableRequest = new CacheableRequest(request, cache).request();
278
279// adding a hook to decompress response
280cacheableRequest.addHook('onResponse', async (value: CacheValue, response: any) => {
281 const buffer = await pm(gunzip)(value.body);
282 value.body = buffer.toString();
283 return value;
284});
285```
286
287here is also an example of how to add in the remote address
288
289```js
290import CacheableRequest, {CacheValue} from 'cacheable-request';
291
292const cacheableRequest = new CacheableRequest(request, cache).request();
293cacheableRequest.addHook('onResponse', (value: CacheValue, response: any) => {
294 if (response.connection) {
295 value.remoteAddress = response.connection.remoteAddress;
296 }
297
298 return value;
299});
300```
301
302### Remove Hooks
303
304You can also remove hook by using below
305
306```js
307CacheableRequest.removeHook('onResponse');
308```
309
310## How to Contribute
311
312Cacheable-Request is an open source package and community driven that is maintained regularly. In addtion we have a couple of other guidelines for review:
313
314* [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - Our code of conduct
315* [CONTRIBUTING.md](CONTRIBUTING.md) - How to contribute to this project
316* [SECURITY.md](SECURITY.md) - Security guidelines and supported versions
317
318## Post an Issue
319
320To post an issue, navigate to the "Issues" tab in the main repository, and then select "New Issue." Enter a clear title describing the issue, as well as a description containing additional relevant information. Also select the label that best describes your issue type. For a bug report, for example, create an issue with the label "bug." In the description field, Be sure to include replication steps, as well as any relevant error messages.
321
322If you're reporting a security violation, be sure to check out the project's [security policy](SECURITY.md).
323
324Please also refer to our [Code of Conduct](CODE_OF_CONDUCT.md) for more information on how to report issues.
325
326## Ask a Question
327
328To ask a question, create an issue with the label "question." In the issue description, include the related code and any context that can help us answer your question.
329
330## License
331
332This project is under the [MIT](LICENSE) license.
333
334MIT © Luke Childs 2017-2021
335MIT © Jared Wray 2022