1 | <h1 align="center">
|
2 | <img src="https://img.ideal-postcodes.co.uk/Ideal%20Postcodes%20Core%20Logo@3x.png" alt="Ideal Postcodes Core Interface">
|
3 | </h1>
|
4 |
|
5 | > JavaScript API for api.ideal-postcodes.co.uk
|
6 |
|
7 | [![CI](https://github.com/ideal-postcodes/core-interface/actions/workflows/ci.yml/badge.svg)](https://github.com/ideal-postcodes/core-interface/actions/workflows/ci.yml)
|
8 | ![Cross Browser Testing](https://github.com/ideal-postcodes/core-interface/workflows/Cross%20Browser%20Testing/badge.svg?branch=saucelabs)
|
9 | [![Release](https://github.com/ideal-postcodes/core-interface/workflows/Release/badge.svg)](https://github.com/ideal-postcodes/core-interface/actions)
|
10 | [![codecov](https://codecov.io/gh/ideal-postcodes/core-interface/branch/master/graph/badge.svg)](https://codecov.io/gh/ideal-postcodes/core-interface)
|
11 | ![Dependency Status](https://david-dm.org/ideal-postcodes/core-interface.svg)
|
12 |
|
13 | [![npm version](https://badge.fury.io/js/%40ideal-postcodes%2Fcore-interface.svg)](https://badge.fury.io/js/%40ideal-postcodes%2Fcore-interface)
|
14 | ![npm bundle size (scoped)](https://img.shields.io/bundlephobia/min/@ideal-postcodes/core-interface.svg?color=%234c1&style=popout)
|
15 | ![npm bundle size (scoped)](https://img.shields.io/bundlephobia/minzip/@ideal-postcodes/core-interface.svg?color=%234c1&style=popout)
|
16 | [![install size](https://packagephobia.now.sh/badge?p=@ideal-postcodes/core-interface)](https://packagephobia.now.sh/result?p=@ideal-postcodes/core-interface)
|
17 |
|
18 | `@ideal-postcodes/core-interface` is an environment agnostic implementation of the Ideal Postcodes JavaScript API client interface.
|
19 |
|
20 | If you are looking for the browser or Node.js client which implements this interface, please check out the [downstream clients links](#downstream-clients).
|
21 |
|
22 | ## Links
|
23 |
|
24 | - [API Documentation](https://core-interface.ideal-postcodes.dev/)
|
25 | - [npm Module](https://www.npmjs.com/package/@ideal-postcodes/core-interface)
|
26 | - [GitHub Repository](https://github.com/ideal-postcodes/core-interface)
|
27 | - [Typings Repository](https://github.com/ideal-postcodes/openapi)
|
28 | - [Fixtures Repository](https://github.com/ideal-postcodes/api-fixtures)
|
29 |
|
30 | ## Downstream Clients
|
31 |
|
32 | - [Browser Client Repository](https://github.com/ideal-postcodes/core-browser)
|
33 | - [Bundled Browser Client Repository](https://github.com/ideal-postcodes/core-browser-bundled)
|
34 | - [Node.js Client Repository](https://github.com/ideal-postcodes/core-node)
|
35 | - [Axios-backed Client Repository](https://github.com/ideal-postcodes/core-axios)
|
36 |
|
37 | ## Documentation
|
38 |
|
39 | - [Usage & Configuration](#usage)
|
40 | - [Quick Methods](#quick-methods)
|
41 | - [Resource Methods](#resource-methods)
|
42 | - [Error Handling](#error-handling)
|
43 |
|
44 | ### Methods
|
45 |
|
46 | #### Usage
|
47 |
|
48 | To install, pick one of the following based on your platform
|
49 |
|
50 | ```bash
|
51 | # For browser client
|
52 | npm install @ideal-postcodes/core-browser
|
53 |
|
54 | # For node.js client
|
55 | npm install @ideal-postcodes/core-node
|
56 |
|
57 | # For generic interface (you need to supply your own HTTP agent)
|
58 | npm install @ideal-postcodes/core-interface
|
59 | ```
|
60 |
|
61 | Instantiate a client with
|
62 |
|
63 | ```javascript
|
64 | import { Client } from "@ideal-postcodes/core-<package-type>";
|
65 |
|
66 | const client = new Client({ api_key: "iddqd" });
|
67 |
|
68 | // Only api_key is required by core-node and core-browser - all others are optional
|
69 | // The agentless interface requires explicit configuration
|
70 | ```
|
71 |
|
72 | [Client configuration options](https://core-interface.ideal-postcodes.dev/interfaces/client.config)
|
73 |
|
74 | ---
|
75 |
|
76 | #### Quick Methods
|
77 |
|
78 | The library exposes a number of simple methods to get at the most common tasks when interacting with the API
|
79 |
|
80 | - [Lookup a Postcode](#lookup-a-postcode)
|
81 | - [Search for an Address](#search-for-an-address)
|
82 | - [Search for an Address by UDPRN](#search-for-an-address-by-udprn)
|
83 | - [Search for an Address by UMPRN](#search-for-an-address-by-umprn)
|
84 | - [Check Key Usability](#check-key-usability)
|
85 |
|
86 | #### Lookup a Postcode
|
87 |
|
88 | Return addresses associated with a given `postcode`
|
89 |
|
90 | Invalid postcodes (i.e. postcode not found) return an empty array `[]`
|
91 |
|
92 | ```javascript
|
93 | import { lookupPostcode } from "@ideal-postcodes/core-browser";
|
94 |
|
95 | const postcode = "id11qd";
|
96 |
|
97 | lookupPostcode({ client, postcode }).then(addresses => {
|
98 | console.log(addresses);
|
99 | {
|
100 | postcode: "ID1 1QD",
|
101 | line_1: "2 Barons Court Road",
|
102 | // ...etc...
|
103 | }
|
104 | });
|
105 | ```
|
106 |
|
107 | `lookupPostcode` [docs](https://core-interface.ideal-postcodes.dev/modules/helper_methods#lookupPostcode)
|
108 |
|
109 | #### Search for an Address
|
110 |
|
111 | Return addresses associated with a given `query`
|
112 |
|
113 | ```javascript
|
114 | import { lookupAddress } from "@ideal-postcodes/core-browser";
|
115 |
|
116 | const query = "10 downing street sw1a";
|
117 |
|
118 | lookupAddress({ client, query }).then(addresses => {
|
119 | console.log(addresses);
|
120 | {
|
121 | postcode: "SW1A 2AA",
|
122 | line_1: "Prime Minister & First Lord Of The Treasury",
|
123 | // ...etc...
|
124 | }
|
125 | });
|
126 | ```
|
127 |
|
128 | `lookupAddress` [docs](https://core-interface.ideal-postcodes.dev/modules/helper_methods#lookupAddress)
|
129 |
|
130 | #### Search for an Address by UDPRN
|
131 |
|
132 | Return address for a given `udprn`
|
133 |
|
134 | Invalid UDPRN will return `null`
|
135 |
|
136 | ```javascript
|
137 | import { lookupUdprn } from "@ideal-postcodes/core-browser";
|
138 |
|
139 | const udprn = 23747771;
|
140 |
|
141 | lookupUdprn({ client, udprn }).then(address => {
|
142 | console.log(address);
|
143 | {
|
144 | postcode: "SW1A 2AA",
|
145 | line_1: "Prime Minister & First Lord Of The Treasury",
|
146 | // ...etc...
|
147 | }
|
148 | });
|
149 | ```
|
150 |
|
151 | `lookupUdprn` [docs](https://core-interface.ideal-postcodes.dev/modules/helper_methods#lookupUdprn)
|
152 |
|
153 | #### Search for an Address by UMPRN
|
154 |
|
155 | Return address for a given `umprn`
|
156 |
|
157 | Invalid UMPRN will return `null`
|
158 |
|
159 | ```javascript
|
160 | import { lookupUmprn } from "@ideal-postcodes/core-browser";
|
161 |
|
162 | const umprn = 50906066;
|
163 |
|
164 | lookupUmprn({ client, umprn }).then(address => {
|
165 | console.log(address);
|
166 | {
|
167 | postcode: "CV4 7AL",
|
168 | line_1: "Room 1, Block 1 Arthur Vick",
|
169 | // ...etc...
|
170 | }
|
171 | });
|
172 | ```
|
173 |
|
174 | `lookupUmprn` [docs](https://core-interface.ideal-postcodes.dev/modules/helper_methods#lookupUmprn)
|
175 |
|
176 | #### Check Key Usability
|
177 |
|
178 | Check if a key is currently usable
|
179 |
|
180 | ```javascript
|
181 | checkKeyUsability({ client }).then((key) => {
|
182 | console.log(key.available); // => true
|
183 | });
|
184 | ```
|
185 |
|
186 | `checkKeyUsability` [docs](https://core-interface.ideal-postcodes.dev/modules/helper_methods#checkKeyUsability)
|
187 |
|
188 | ---
|
189 |
|
190 | #### Resources
|
191 |
|
192 | Resources defined in [the API documentation](https://ideal-postcodes.co.uk/documentation) are exported by the library. Each resource exposes a method (`#retrieve`, `#list`, etc) which maps to a resource action.
|
193 |
|
194 | These methods expose a low level interface to execute HTTP requests and observe HTTP responses. They are ideal if you have a more complex query or usecase where low level access would be useful.
|
195 |
|
196 | Resource methods return a promise with a [HTTP response object type](https://core-interface.ideal-postcodes.dev/interfaces/httpresponse.html).
|
197 |
|
198 | #### Retrieve
|
199 |
|
200 | Requesting a resource by ID (e.g. a postcode lookup for postcode with ID "SW1A 2AA") maps to the `#retrieve` method.
|
201 |
|
202 | The first argument is the client object. The second is the resource ID. The last argument is an object which accepts `header` and `query` attributes that map to HTTP header and the request querystring.
|
203 |
|
204 | ```javascript
|
205 | resourceName.retrieve(client, "id", {
|
206 | query: {
|
207 | api_key: "foo",
|
208 | tags: "this,that,those",
|
209 | licensee: "sk_99dj3",
|
210 | },
|
211 | header: {
|
212 | "IDPC-Source-IP": "8.8.8.8",
|
213 | },
|
214 | timeout: 5000,
|
215 | });
|
216 | ```
|
217 |
|
218 | #### List
|
219 |
|
220 | Requesting a resource endpoint (e.g. an address query to `/addresses`) maps to the `#list` method.
|
221 |
|
222 | ```javascript
|
223 | resourceName.list(client, {
|
224 | query: {
|
225 | api_key: "foo",
|
226 | query: "10 downing street",
|
227 | },
|
228 | header: {
|
229 | "IDPC-Source-IP": "8.8.8.8",
|
230 | },
|
231 | timeout: 5000,
|
232 | });
|
233 | ```
|
234 |
|
235 | The first argument is the client. The second is an object which accepts `header` and `query` attributes that map to HTTP header and the request querystring.
|
236 |
|
237 | #### Custom Actions
|
238 |
|
239 | Some endpoints are defined as custom actions, e.g. `/keys/:key/usage`. These can be invoked using the name of the custom action.
|
240 |
|
241 | E.g. for [key usage data extraction](https://ideal-postcodes.co.uk/documentation/keys#usage)
|
242 |
|
243 | ```javascript
|
244 | keys.usage(client, api_key, {
|
245 | query: {
|
246 | tags: "checkout,production",
|
247 | },
|
248 | header: {
|
249 | Authorization: 'IDEALPOSTCODES user_token="foo"',
|
250 | },
|
251 | timeout: 5000,
|
252 | });
|
253 | ```
|
254 |
|
255 | ---
|
256 |
|
257 | #### Resource Methods
|
258 |
|
259 | Listed below are the available resources exposed by the library:
|
260 |
|
261 | - [Postcodes](#postcodes)
|
262 | - [Addresses](#addresses)
|
263 | - [Autocomplete](#autocomplete)
|
264 | - [UDPRN](#udprn)
|
265 | - [UMPRN](#umprn)
|
266 | - [Keys](#keys)
|
267 |
|
268 | #### Postcodes
|
269 |
|
270 | Retrieve addresses for a postcode.
|
271 |
|
272 | ```javascript
|
273 | import { postcodes } from "@ideal-postcodes/core-browser";
|
274 | postcodes
|
275 | .retrieve(client, "SW1A2AA", {
|
276 | header: {
|
277 | Authorization: 'IDEALPOSTCODES api_key="iddqd"',
|
278 | },
|
279 | })
|
280 | .then((response) => {
|
281 | const addresses = response.body.result;
|
282 | })
|
283 | .catch((error) => logger(error));
|
284 | ```
|
285 |
|
286 | [Postcode resource HTTP API documentation](https://ideal-postcodes.co.uk/documentation/postcodes)
|
287 |
|
288 | [Postcode resource docs](https://core-interface.ideal-postcodes.dev/interfaces/postcoderesource.html)
|
289 |
|
290 | #### Addresses
|
291 |
|
292 | Search for an address
|
293 |
|
294 | ```javascript
|
295 | import { addresses } from "@ideal-postcodes/core-browser";
|
296 |
|
297 | addresses
|
298 | .list(client, {
|
299 | query: {
|
300 | query: "10 Downing street",
|
301 | },
|
302 | header: {
|
303 | Authorization: 'IDEALPOSTCODES api_key="iddqd"',
|
304 | },
|
305 | })
|
306 | .then((response) => {
|
307 | const addresses = response.body.result.hits;
|
308 | })
|
309 | .catch((error) => logger(error));
|
310 | ```
|
311 |
|
312 | [Address resource HTTP API documentation](https://ideal-postcodes.co.uk/documentation/addresses)
|
313 |
|
314 | [Address resource client docs](https://core-interface.ideal-postcodes.dev/modules/resources_addresses.html)
|
315 |
|
316 | #### Autocomplete
|
317 |
|
318 | Autocomplete an address given an address partial
|
319 |
|
320 | ```javascript
|
321 | import { autocomplete } from "@ideal-postcodes/core-browser";
|
322 |
|
323 | autocomplete
|
324 | .list(client, {
|
325 | query: {
|
326 | query: "10 Downing stre",
|
327 | },
|
328 | header: {
|
329 | Authorization: 'IDEALPOSTCODES api_key="iddqd"',
|
330 | },
|
331 | })
|
332 | .then((response) => {
|
333 | const suggestions = response.body.result.hits;
|
334 | })
|
335 | .catch((error) => logger(error));
|
336 | ```
|
337 |
|
338 | [Autocomplete resource HTTP API documentation](https://ideal-postcodes.co.uk/documentation/autocomplete)
|
339 |
|
340 | [Autocomplete resource client docs](https://core-interface.ideal-postcodes.dev/modules/resources_autocomplete.html)
|
341 |
|
342 | #### UDPRN
|
343 |
|
344 | Retrieve an address given a UDPRN
|
345 |
|
346 | ```javascript
|
347 | import { udprn } from "@ideal-postcodes/core-browser";
|
348 |
|
349 | udprn
|
350 | .retrieve(client, "12345678", {
|
351 | header: {
|
352 | Authorization: 'IDEALPOSTCODES api_key="iddqd"',
|
353 | },
|
354 | })
|
355 | .then((response) => {
|
356 | const address = response.body.result;
|
357 | })
|
358 | .catch((error) => logger(error));
|
359 | ```
|
360 |
|
361 | [UDPRN resource HTTP API documentation](https://ideal-postcodes.co.uk/documentation/udprn)
|
362 |
|
363 | [UDPRN resource client docs](https://core-interface.ideal-postcodes.dev/modules/resources_udprn.html)
|
364 |
|
365 | #### UMPRN
|
366 |
|
367 | Retrieve a multiple residence premise given a UMPRN
|
368 |
|
369 | ```javascript
|
370 | import { umprn } from "@ideal-postcodes/core-browser";
|
371 |
|
372 | umprn
|
373 | .retrieve(client, "87654321", {
|
374 | header: {
|
375 | Authorization: 'IDEALPOSTCODES api_key="iddqd"',
|
376 | },
|
377 | })
|
378 | .then((response) => {
|
379 | const address = response.body.result;
|
380 | })
|
381 | .catch((error) => logger(error));
|
382 | ```
|
383 |
|
384 | [UMPRN resource HTTP API documentation](https://ideal-postcodes.co.uk/documentation/umprn)
|
385 |
|
386 | [UMPRN resource client docs](https://core-interface.ideal-postcodes.dev/modules/resources_umprn.html)
|
387 |
|
388 | #### Keys
|
389 |
|
390 | Find out if a key is available
|
391 |
|
392 | ```javascript
|
393 | import { keys } from "@ideal-postcodes/core-browser";
|
394 |
|
395 | keys
|
396 | .retrieve(client, "iddqd", {})
|
397 | .then((response) => {
|
398 | const { available } = response.body.result;
|
399 | })
|
400 | .catch((error) => logger(error));
|
401 | ```
|
402 |
|
403 | [Method docs](https://core-interface.ideal-postcodes.dev/modules/resources_keys.html#retrieve)
|
404 |
|
405 | Get private information on key (requires user_token)
|
406 |
|
407 | ```javascript
|
408 | import { keys } from "@ideal-postcodes/core-browser";
|
409 |
|
410 | keys
|
411 | .retrieve(client, "iddqd", {
|
412 | header: {
|
413 | Authorization: 'IDEALPOSTCODES user_token="secret-token"',
|
414 | },
|
415 | })
|
416 | .then((response) => {
|
417 | const key = response.body.result;
|
418 | })
|
419 | .catch((error) => logger(error));
|
420 | ```
|
421 |
|
422 | [Method docs](https://core-interface.ideal-postcodes.dev/modules/resources_keys.html#retrieve)
|
423 |
|
424 | Get key usage data
|
425 |
|
426 | ```javascript
|
427 | import { keys } from "@ideal-postcodes/core-browser";
|
428 |
|
429 | keys
|
430 | .usage(client, "iddqd", {
|
431 | header: {
|
432 | Authorization: 'IDEALPOSTCODES user_token="secret-token"',
|
433 | },
|
434 | })
|
435 | .then((response) => {
|
436 | const key = response.body.result;
|
437 | })
|
438 | .catch((error) => logger(error));
|
439 | ```
|
440 |
|
441 | [Method docs](https://core-interface.ideal-postcodes.dev/modules/resources_keys.html#usage)
|
442 |
|
443 | [Keys resource HTTP API documentation](https://ideal-postcodes.co.uk/documentation/keys)
|
444 |
|
445 | [Key resource client docs](https://core-interface.ideal-postcodes.dev/modules/resources_keys.html)
|
446 |
|
447 | ---
|
448 |
|
449 | #### Error Handling
|
450 |
|
451 | This library exports a static variable `errors` which contains custom error constructors that wrap specific API errors. These constructors can be used to test for specific cases using the `instanceof` operator.
|
452 |
|
453 | For example:
|
454 |
|
455 | ```javascript
|
456 | const { IdpcInvalidKeyError } = Client.errors;
|
457 |
|
458 | try {
|
459 | const addresses = await lookupPostcode({ client, postcode: "SW1A2AA" });
|
460 | } catch (error) {
|
461 | if (error instanceof IdpcInvalidKeyError) {
|
462 | // Handle an invalid key error
|
463 | }
|
464 | }
|
465 | ```
|
466 |
|
467 | Not all specific API errors will be caught. If a specific API error does not have an error constructor defined, a more generic error (determined by the HTTP status code) will be returned.
|
468 |
|
469 | For example:
|
470 |
|
471 | ```javascript
|
472 | import {
|
473 | IdpcRequestFailedError,
|
474 | lookupPostcode,
|
475 | } from "@ideal-postcodes/core-browser";
|
476 |
|
477 | try {
|
478 | const addresses = await lookupPostcode({ client, postcode: "SW1A2AA" });
|
479 | } catch (error) {
|
480 | if (error instanceof IdpcRequestFailedError) {
|
481 | // IdpcRequestFailedError indicates a 402 response code
|
482 | // Possibly the key balance has been depleted
|
483 | }
|
484 | }
|
485 | ```
|
486 |
|
487 | You may view a [sketch of the error prototype chain](#error-prototype-chain).
|
488 |
|
489 | ---
|
490 |
|
491 | ### Core Interface Errors
|
492 |
|
493 | For more advanced use cases, this core-interface library provides:
|
494 |
|
495 | - Class implementations for [Ideal Postcodes API errors](https://core-interface.ideal-postcodes.dev/classes/errors.idpcapierror) that inherit from `Error`
|
496 | - A [parser](https://core-interface.ideal-postcodes.dev/modules/errors#parse) that converts raw error data into one of these error instances
|
497 |
|
498 | #### Error Usage
|
499 |
|
500 | Aside from inspecting the HTTP request status code and/or JSON body response codes, you may also test for specific error instances.
|
501 |
|
502 | Errors that don't inherit from [`IdealPostcodesError`](https://core-interface.ideal-postcodes.dev/classes/errors.idealpostcodeserror) would indicate some kind of error external to the API (e.g. bad network, request timeout).
|
503 |
|
504 | ```javascript
|
505 | import { errors } from "@ideal-postcodes/core-browser";
|
506 | const { IdpcPostcodeNotFoundError } = errors;
|
507 |
|
508 | // Handle a specific error
|
509 | if (error instanceof IdpcPostcodeNotFoundError) {
|
510 | // You got yourself an invalid API Key
|
511 | }
|
512 |
|
513 | // Alternatively use a switch statement
|
514 | switch (true) {
|
515 | case error instanceof IdpcPostcodeNotFoundError:
|
516 | // You got yourself an invalid API Key
|
517 | default:
|
518 | // Default error handling path
|
519 | }
|
520 | ```
|
521 |
|
522 | #### Error Prototype Chain
|
523 |
|
524 | All errors inherit from JavaScript's `Error` prototype.
|
525 |
|
526 | Errors are grouped by HTTP status code classes.
|
527 |
|
528 | Specific errors may be supplied for the following reasons:
|
529 |
|
530 | - Convenience. They are frequently tested for (e.g. invalid postcode, postcode not found)
|
531 | - Useful for debug purposes during the implementation stages
|
532 |
|
533 | ```javascript
|
534 | Prototype Chain
|
535 |
|
536 | # Parent class inherits from Javascript Error. Returned if no JSON Response body
|
537 | IdealPostcodesError < Error
|
538 | |
|
539 | |- IdpcApiError # Generic Error Class, returned if JSON response body present
|
540 | |
|
541 | |- IdpcBadRequestError # 400 Errors
|
542 | |- IdpcUnauthorisedError # 401 Errors
|
543 | |- IdpcRequestFailedError # 402 Errors
|
544 | | |- IdpcBalanceDepletedError
|
545 | | |- IdpcLimitReachedError
|
546 | |
|
547 | |- IdpcResourceNotFoundError # 404 Errors
|
548 | | |- IdpcPostcodeNotFoundError
|
549 | | |- IdpcKeyNotFoundError
|
550 | | |- IdpcUdprnNotFoundError
|
551 | | |- IdpcUmprnNotFoundError
|
552 | |
|
553 | |- IdpcServerError # 500 Errors
|
554 | ```
|
555 |
|
556 | #### Error Parser
|
557 |
|
558 | The error parser consumes a HTTP API response and returns the correct error instance.
|
559 |
|
560 | ```javascript
|
561 | import { errors } from "@ideal-postcodes/core-browser";
|
562 | const { parse, IdpcPostcodeNotFoundError } = errors;
|
563 |
|
564 | const invalidPostcodeUrl = "https://api.ideal-postcodes.co.uk/v1/postcodes/bad_postcode?api_key=iddqd"
|
565 |
|
566 | const response = await fetch(invalidPostcodeUrl);
|
567 |
|
568 | // Generate an error object if present, otherwise returns `undefined`
|
569 | const error = parse(response);
|
570 |
|
571 | // Handle the error
|
572 | if (error instanceof IdpcPostcodeNotFoundError) {...}
|
573 | ```
|
574 |
|
575 | ## Test
|
576 |
|
577 | ```bash
|
578 | npm test
|
579 | ```
|
580 |
|
581 | ## Licence
|
582 |
|
583 | MIT
|