|
1 | 1 | # Fetch
|
2 |
| -Tiny wrapper around DOM fetch for common API wrappings. |
| 2 | +Tiny wrapper around DOM fetch for common API wrappings. Isomorphic (supports browsers and Node.js), if `fetch` is available or polyfilled. |
3 | 3 |
|
4 | 4 | [](https://npm.servalldatasystems.com/#/detail/@lcdev/fetch)
|
5 | 5 |
|
6 | 6 | ```bash
|
7 | 7 | yarn add @lcdev/fetch@VERSION
|
8 | 8 | ```
|
9 | 9 |
|
10 |
| -Use: |
| 10 | +Features: |
| 11 | +- Easy to use builder-style API |
| 12 | +- Quick JSON, blob and text parsing options |
| 13 | +- Shareable builders for common options (authorization headers, onResponse hooks, etc.) |
| 14 | +- No magic - call `build()` and pass to fetch if you want |
| 15 | +- TypeScript friendly |
| 16 | +- Tiny footprint (2kb) |
11 | 17 |
|
12 |
| -```typescript |
13 |
| -import { HttpMethod, api, apiCall } from '@lcdev/fetch'; |
| 18 | +If you are looking for something not available here, try [ky-universal](https://github.com/sindresorhus/ky-universal) or [axios](https://github.com/axios/axios). |
14 | 19 |
|
15 |
| -// one-off requests are easy |
16 |
| -await apiCall('https://base-url.com/endpoint').json<MyReturnType>(); |
| 20 | +There are two main functions exported by this package: |
17 | 21 |
|
18 |
| -// re-use this any time you want to make a call to this api |
19 |
| -const myCoreApi = api('https://base-url.com'); |
| 22 | +1. The `apiCall` function, which is used for creating a one-off fetch request |
| 23 | +2. The `api` function, which creates a shared builder for many fetch requests |
20 | 24 |
|
21 |
| -// calling .then or `await` triggers the request |
22 |
| -await myCoreApi.get('/endpoint') |
23 |
| - // chainable interface |
24 |
| - .withBody({ foo: 'bar' }) |
25 |
| - .withQuery({ baz: 'bat' }) |
26 |
| - // chain .json if you know the response is json |
27 |
| - .json<MyReturnType>(); |
28 |
| -``` |
| 25 | +### `apiCall` |
| 26 | +The simplest function is `apiCall`, which sets up a fetch request. |
29 | 27 |
|
30 |
| -Requests start on await/then. Chain to add data to the request. This is just a thin way to make `fetch` calls. |
| 28 | +```typescript |
| 29 | +import { HttpMethod, apiCall } from '@lcdev/fetch'; |
| 30 | + |
| 31 | +await apiCall('https://base-url.com/endpoint', HttpMethod.GET).json<TheResponseObject>(); |
| 32 | +``` |
31 | 33 |
|
32 |
| -'Api's can have global 'transforms' which can do things with `withBearerToken`, `onResponse`, etc. |
33 |
| -The common use is for authorization tokens. |
| 34 | +This can be shortened by using the http method aliases exported by this package. |
34 | 35 |
|
35 | 36 | ```typescript
|
36 |
| -// let's assume that this is something that manages the current token |
37 |
| -const authManager = { |
38 |
| - token: '...', |
39 |
| -}; |
40 |
| - |
41 |
| -const myCoreApi = api('https://base-url.com') |
42 |
| - // whenever a request is made, this gets `authManager.token` and attachs it to the Authorization header |
43 |
| - .withBearerToken(authManager); |
| 37 | +import { get } from '@lcdev/fetch'; |
| 38 | + |
| 39 | +await get('https://base-url.com/endpoint').json<TheResponseObject>(); |
44 | 40 | ```
|
45 | 41 |
|
46 |
| -You can add little callbacks to `myCoreApi` using `onResponse` or `onJsonResponse`. You might |
47 |
| -do so to watch for 401 responses, or maybe just for logging. |
| 42 | +There are `get`, `post`, `put`, `patch`, and `remove` aliases. |
| 43 | + |
| 44 | +With a `ApiCall` builder (the object returned by `apiCall`), we can chain many options for the request. |
48 | 45 |
|
49 |
| -Chainable methods for API calls: |
50 |
| -- `withBody(object, isJson?: boolean)`: adds json or other type of request body |
51 |
| -- `withQuery(object)`: adds query parameters |
52 |
| -- `withBearerToken(BearerToken)`: adds Authorization: Bearer {token} header |
53 |
| -- `withContentType(string)`: changes default content type header |
| 46 | +- `withQuery(object, options?: SerializationOptions)`: adds query parameters, stringifying the object with `query-string` |
54 | 47 | - `withHeaders(Headers)`: adds headers to request
|
55 |
| -- `withHeader(key, value)`: adds a header to the request |
56 |
| -- `expectStatus(number)`: throw error is response status isn't the expect one |
57 |
| -- `build()`: constructs options that can be passed into `fetch` |
| 48 | +- `withHeader(key, value)`: adds a single header to the request |
| 49 | +- `withContentType(string)`: changes the content-type header |
| 50 | +- `withBearerToken(object: { token?: string })`: adds `Authorization: Bearer {token}` header |
| 51 | +- `withBody(object, isJson?: boolean, options?: SerializationOptions)`: adds a request body |
| 52 | +- `withJsonBody(object, options?: SerializationOptions)`: adds JSON request body |
| 53 | +- `withFormDataBody(FormData, options?: SerializationOptions)`: adds form-data request body |
| 54 | +- `withURLEncodedBody(object, options?: SerializationOptions)`: adds 'application/x-www-form-urlencoded' request body |
| 55 | +- `expectStatus(number)`: throw an error if the response status isn't the expected one |
| 56 | +- `expectSuccessStatus()`: throw an error if the response status isn't in 200 range |
| 57 | +- `onResponse(callback)`: calls your function whenever responses are received |
| 58 | +- `onJsonResponse(callback)`: calls your function whenever JSON responses are received |
| 59 | +- `build()`: constructs options that can be passed into `fetch` directly |
58 | 60 | - `json<T>()`: calls fetch and parses response as JSON
|
59 |
| -- `jsonAndResponse<T>()`: calls fetch and parses response as JSON, along with full Response |
| 61 | +- `jsonAndResponse<T>()`: calls fetch and parses response as JSON, along with the full Response object |
60 | 62 | - `blob<T>()`: calls fetch and parses response as a blob
|
61 |
| -- `blobAndResponse<T>()`: calls fetch and parses response as a blob, along with full Response |
| 63 | +- `blobAndResponse<T>()`: calls fetch and parses response as a blob, along with the full Response object |
| 64 | +- `text<T>()`: calls fetch and parses response as text |
| 65 | +- `textAndResponse<T>()`: calls fetch and parses response as text, along with the full Response object |
| 66 | + |
| 67 | +Because we expose `build`, there is always an escape hatch if you need something non-standard. |
| 68 | + |
| 69 | +Note that fetch calls are **lazy** - meaning that nothing will run until you call `.then` or `await` it. |
62 | 70 |
|
63 |
| -A base API itself can be called with `.get`, `.post`, etc. You can change the base URL if required |
64 |
| -with `changeBaseURL(path)`, though be warned that every request from then on will then be based on that. |
| 71 | +### `api` |
| 72 | +Most of the time, we make web apps that call APIs many times in different ways (endpoints, authorization, etc.). |
| 73 | +This package provides a way to share configuration easily between all calls, without being "global". |
| 74 | + |
| 75 | +```typescript |
| 76 | +import { api } from '@lcdev/fetch'; |
| 77 | + |
| 78 | +const myBackend = api('https://base-url.com') |
| 79 | + .withBearerToken({ token: '...' }) |
| 80 | + .onResponse((res) => { |
| 81 | + if (res.status === 401) logout(); |
| 82 | + }); |
| 83 | + |
| 84 | +// we can re-use myBackend where we want to |
| 85 | +// you might put myBackend in a React Context, or inject it into state management |
| 86 | +await myBackend.get('/endpoint').json<TheResponseObject>(); |
| 87 | +await myBackend.post('/endpoint').withJsonBody({ foo: 'bar' }).json<TheOtherResponse>(); |
| 88 | +``` |
| 89 | + |
| 90 | +Here, `myBackend` is an `Api` object, which exposes ways to create `ApiCall`s (like above). |
| 91 | +You can perform the same builder functions on these as with `apiCall`. |
| 92 | + |
| 93 | +You can add little callbacks to `myBackend` using `onResponse` or `onJsonResponse`. You might |
| 94 | +do this for logging, for business logic, etc. |
| 95 | + |
| 96 | +You can change the base URL if required with `changeBaseURL(path)`, though be warned that |
| 97 | +every request from then on will then be based on that. |
65 | 98 |
|
66 | 99 | ## NodeJS Support
|
67 | 100 | Just polyfill `fetch`, and this package will work. Install `cross-fetch` package and add the following to your main file.
|
68 | 101 |
|
| 102 | +```bash |
| 103 | +yarn add cross-fetch@3 |
| 104 | +``` |
| 105 | + |
69 | 106 | ```typescript
|
70 | 107 | import 'cross-fetch/polyfill';
|
71 | 108 | ```
|
0 commit comments