1 | # cacheable-request
|
2 |
|
3 | > Wrap native HTTP requests with RFC compliant cache support
|
4 |
|
5 | [![Build Status](https://travis-ci.org/lukechilds/cacheable-request.svg?branch=master)](https://travis-ci.org/lukechilds/cacheable-request)
|
6 | [![Coverage Status](https://coveralls.io/repos/github/lukechilds/cacheable-request/badge.svg?branch=master)](https://coveralls.io/github/lukechilds/cacheable-request?branch=master)
|
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 | ## Features
|
15 |
|
16 | - Only stores cacheable responses as defined by RFC 7234
|
17 | - Fresh cache entries are served directly from cache
|
18 | - Stale cache entries are revalidated with `If-None-Match`/`If-Modified-Since` headers
|
19 | - 304 responses from revalidation requests use cached body
|
20 | - Updates `Age` header on cached responses
|
21 | - Can completely bypass cache on a per request basis
|
22 | - In memory cache by default
|
23 | - Official support for Redis, MongoDB, SQLite, PostgreSQL and MySQL storage adapters
|
24 | - Easily plug in your own or third-party storage adapters
|
25 | - If DB connection fails, cache is automatically bypassed ([disabled by default](#optsautomaticfailover))
|
26 | - Adds cache support to any existing HTTP code with minimal changes
|
27 | - Uses [http-cache-semantics](https://github.com/pornel/http-cache-semantics) internally for HTTP RFC 7234 compliance
|
28 |
|
29 | ## Install
|
30 |
|
31 | ```shell
|
32 | npm install cacheable-request
|
33 | ```
|
34 |
|
35 | ## Usage
|
36 |
|
37 | ```js
|
38 | const http = require('http');
|
39 | const CacheableRequest = require('cacheable-request');
|
40 |
|
41 | // Then instead of
|
42 | const req = http.request('http://example.com', cb);
|
43 | req.end();
|
44 |
|
45 | // You can do
|
46 | const cacheableRequest = new CacheableRequest(http.request);
|
47 | const cacheReq = cacheableRequest('http://example.com', cb);
|
48 | cacheReq.on('request', req => req.end());
|
49 | // Future requests to 'example.com' will be returned from cache if still valid
|
50 |
|
51 | // You pass in any other http.request API compatible method to be wrapped with cache support:
|
52 | const cacheableRequest = new CacheableRequest(https.request);
|
53 | const cacheableRequest = new CacheableRequest(electron.net);
|
54 | ```
|
55 |
|
56 | ## Storage Adapters
|
57 |
|
58 | `cacheable-request` uses [Keyv](https://github.com/lukechilds/keyv) to support a wide range of storage adapters.
|
59 |
|
60 | For example, to use Redis as a cache backend, you just need to install the official Redis Keyv storage adapter:
|
61 |
|
62 | ```
|
63 | npm install @keyv/redis
|
64 | ```
|
65 |
|
66 | And then you can pass `CacheableRequest` your connection string:
|
67 |
|
68 | ```js
|
69 | const cacheableRequest = new CacheableRequest(http.request, 'redis://user:pass@localhost:6379');
|
70 | ```
|
71 |
|
72 | [View all official Keyv storage adapters.](https://github.com/lukechilds/keyv#official-storage-adapters)
|
73 |
|
74 | Keyv also supports anything that follows the Map API so it's easy to write your own storage adapter or use a third-party solution.
|
75 |
|
76 | e.g The following are all valid storage adapters
|
77 |
|
78 | ```js
|
79 | const storageAdapter = new Map();
|
80 | // or
|
81 | const storageAdapter = require('./my-storage-adapter');
|
82 | // or
|
83 | const QuickLRU = require('quick-lru');
|
84 | const storageAdapter = new QuickLRU({ maxSize: 1000 });
|
85 |
|
86 | const cacheableRequest = new CacheableRequest(http.request, storageAdapter);
|
87 | ```
|
88 |
|
89 | View the [Keyv docs](https://github.com/lukechilds/keyv) for more information on how to use storage adapters.
|
90 |
|
91 | ## API
|
92 |
|
93 | ### new cacheableRequest(request, [storageAdapter])
|
94 |
|
95 | Returns the provided request function wrapped with cache support.
|
96 |
|
97 | #### request
|
98 |
|
99 | Type: `function`
|
100 |
|
101 | Request 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.
|
102 |
|
103 | #### storageAdapter
|
104 |
|
105 | Type: `Keyv storage adapter`<br>
|
106 | Default: `new Map()`
|
107 |
|
108 | A [Keyv](https://github.com/lukechilds/keyv) storage adapter instance, or connection string if using with an official Keyv storage adapter.
|
109 |
|
110 | ### Instance
|
111 |
|
112 | #### cacheableRequest(opts, [cb])
|
113 |
|
114 | Returns an event emitter.
|
115 |
|
116 | ##### opts
|
117 |
|
118 | Type: `object`, `string`
|
119 |
|
120 | - Any of the default request functions options.
|
121 | - Any [`http-cache-semantics`](https://github.com/kornelski/http-cache-semantics#constructor-options) options.
|
122 | - Any of the following:
|
123 |
|
124 | ###### opts.cache
|
125 |
|
126 | Type: `boolean`<br>
|
127 | Default: `true`
|
128 |
|
129 | If the cache should be used. Setting this to false will completely bypass the cache for the current request.
|
130 |
|
131 | ###### opts.strictTtl
|
132 |
|
133 | Type: `boolean`<br>
|
134 | Default: `false`
|
135 |
|
136 | If set to `true` once a cached resource has expired it is deleted and will have to be re-requested.
|
137 |
|
138 | If 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.
|
139 |
|
140 | ###### opts.maxTtl
|
141 |
|
142 | Type: `number`<br>
|
143 | Default: `undefined`
|
144 |
|
145 | Limits TTL. The `number` represents milliseconds.
|
146 |
|
147 | ###### opts.automaticFailover
|
148 |
|
149 | Type: `boolean`<br>
|
150 | Default: `false`
|
151 |
|
152 | When 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.
|
153 |
|
154 | ###### opts.forceRefresh
|
155 |
|
156 | Type: `boolean`<br>
|
157 | Default: `false`
|
158 |
|
159 | Forces refreshing the cache. If the response could be retrieved from the cache, it will perform a new request and override the cache instead.
|
160 |
|
161 | ##### cb
|
162 |
|
163 | Type: `function`
|
164 |
|
165 | The callback function which will receive the response as an argument.
|
166 |
|
167 | The 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.
|
168 |
|
169 | ##### .on('request', request)
|
170 |
|
171 | `request` event to get the request object of the request.
|
172 |
|
173 | **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.
|
174 |
|
175 | ##### .on('response', response)
|
176 |
|
177 | `response` event to get the response object from the HTTP request or cache.
|
178 |
|
179 | ##### .on('error', error)
|
180 |
|
181 | `error` event emitted in case of an error with the cache.
|
182 |
|
183 | Errors 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.
|
184 |
|
185 | To properly handle all error scenarios you should use the following pattern:
|
186 |
|
187 | ```js
|
188 | cacheableRequest('example.com', cb)
|
189 | .on('error', err => {
|
190 | if (err instanceof CacheableRequest.CacheError) {
|
191 | handleCacheError(err); // Cache error
|
192 | } else if (err instanceof CacheableRequest.RequestError) {
|
193 | handleRequestError(err); // Request function thrown
|
194 | }
|
195 | })
|
196 | .on('request', req => {
|
197 | req.on('error', handleRequestError); // Request error emitted
|
198 | req.end();
|
199 | });
|
200 | ```
|
201 |
|
202 | **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.
|
203 |
|
204 | ## License
|
205 |
|
206 | MIT © Luke Childs
|