1 | # Apisauce
|
2 |
|
3 | ```
|
4 | (Ring ring ring)
|
5 | < Hello?
|
6 | > Hi, can I speak to JSON API.
|
7 | < Speaking.
|
8 | > Hi, it's me JavaScript. Look, we need to talk.
|
9 | < Now is not a good time...
|
10 | > Wait, I just wanted to say, sorry.
|
11 | < ...
|
12 | ```
|
13 |
|
14 | Talking to APIs doesn't have to be awkward anymore.
|
15 |
|
16 | [![npm module](https://badge.fury.io/js/apisauce.svg)](https://www.npmjs.org/package/apisauce)
|
17 |
|
18 | # Features
|
19 |
|
20 | - low-fat wrapper for the amazing `axios` http client library
|
21 | - all responses follow the same flow: success and failure alike
|
22 | - responses have a `problem` property to help guide exception flow
|
23 | - attach functions that get called each request
|
24 | - attach functions that change all request or response data
|
25 | - detects connection issues (on React Native)
|
26 |
|
27 | # Installing
|
28 |
|
29 | `npm i apisauce --save` or `yarn add apisauce`
|
30 |
|
31 | - Depends on `axios`.
|
32 | - Compatible with ES5.
|
33 | - Built with TypeScript.
|
34 | - Supports Node, the browser, and React Native.
|
35 |
|
36 | # Quick Start
|
37 |
|
38 | ```js
|
39 | // showLastCommitMessageForThisLibrary.js
|
40 | import { create } from 'apisauce'
|
41 |
|
42 | // define the api
|
43 | const api = create({
|
44 | baseURL: 'https://api.github.com',
|
45 | headers: { Accept: 'application/vnd.github.v3+json' },
|
46 | })
|
47 |
|
48 | // start making calls
|
49 | api
|
50 | .get('/repos/skellock/apisauce/commits')
|
51 | .then(response => response.data[0].commit.message)
|
52 | .then(console.log)
|
53 |
|
54 | // customizing headers per-request
|
55 | api.post('/users', { name: 'steve' }, { headers: { 'x-gigawatts': '1.21' } })
|
56 | ```
|
57 |
|
58 | See the examples folder for more code.
|
59 |
|
60 | # Documentation
|
61 |
|
62 | ## Create an API
|
63 |
|
64 | You create an api by calling `.create()` and passing in a configuration object.
|
65 |
|
66 | ```js
|
67 | const api = create({ baseURL: 'https://api.github.com' })
|
68 | ```
|
69 |
|
70 | The only required property is `baseURL` and it should be the starting point for
|
71 | your API. It can contain a sub-path and a port as well.
|
72 |
|
73 | ```js
|
74 | const api = create({ baseURL: 'https://example.com/api/v3' })
|
75 | ```
|
76 |
|
77 | HTTP request headers for all requests can be included as well.
|
78 |
|
79 | ```js
|
80 | const api = create({
|
81 | baseURL: '...',
|
82 | headers: {
|
83 | 'X-API-KEY': '123',
|
84 | 'X-MARKS-THE-SPOT': 'yarrrrr',
|
85 | },
|
86 | })
|
87 | ```
|
88 |
|
89 | Default timeouts can be applied too:
|
90 |
|
91 | ```js
|
92 | const api = create({ baseURL: '...', timeout: 30000 }) // 30 seconds
|
93 | ```
|
94 |
|
95 | You can also pass an already created axios instance
|
96 |
|
97 | ```js
|
98 | import axios from 'axios'
|
99 | import { create } from 'apisauce'
|
100 |
|
101 | const customAxiosInstance = axios.create({ baseURL: 'https://example.com/api/v3' })
|
102 |
|
103 | const apisauceInstance = create({ axiosInstance: customAxiosInstance })
|
104 | ```
|
105 |
|
106 | ## Calling The API
|
107 |
|
108 | With your fresh `api`, you can now call it like this:
|
109 |
|
110 | ```js
|
111 | api.get('/repos/skellock/apisauce/commits')
|
112 | api.head('/me')
|
113 | api.delete('/users/69')
|
114 | api.post('/todos', { note: 'jump around' }, { headers: { 'x-ray': 'machine' } })
|
115 | api.patch('/servers/1', { live: false })
|
116 | api.put('/servers/1', { live: true })
|
117 | api.link('/images/my_dog.jpg', {}, { headers: { Link: '<http://example.com/profiles/joe>; rel="tag"' } })
|
118 | api.unlink('/images/my_dog.jpg', {}, { headers: { Link: '<http://example.com/profiles/joe>; rel="tag"' } })
|
119 | api.any({ method: 'GET', url: '/product', params: { id: 1 } })
|
120 | ```
|
121 |
|
122 | `get`, `head`, `delete`, `link` and `unlink` accept 3 parameters:
|
123 |
|
124 | - url - the relative path to the API (required)
|
125 | - params - Object - query string variables (optional)
|
126 | - axiosConfig - Object - config passed along to the `axios` request (optional)
|
127 |
|
128 | `post`, `put`, and `patch` accept 3 different parameters:
|
129 |
|
130 | - url - the relative path to the API (required)
|
131 | - data - Object - the object jumping the wire
|
132 | - axiosConfig - Object - config passed along to the `axios` request (optional)
|
133 |
|
134 | `any` only accept one parameter
|
135 |
|
136 | - config - Object - config passed along to the `axios` request, this object same as `axiosConfig`
|
137 |
|
138 | ## Responses
|
139 |
|
140 | The responses are promise-based, so you'll need to handle things in a
|
141 | `.then()` function.
|
142 |
|
143 | The promised is always resolved with a `response` object.
|
144 |
|
145 | Even if there was a problem with the request! This is one of the goals of
|
146 | this library. It ensures sane calling code without having to handle `.catch`
|
147 | and have 2 separate flows.
|
148 |
|
149 | A response will always have these 2 properties:
|
150 |
|
151 | ```
|
152 | ok - Boolean - True if the status code is in the 200's; false otherwise.
|
153 | problem - String - One of 6 different values (see below - problem codes)
|
154 | ```
|
155 |
|
156 | If the request made it to the server and got a response of any kind, response
|
157 | will also have these properties:
|
158 |
|
159 | ```
|
160 | data - Object - this is probably the thing you're after.
|
161 | status - Number - the HTTP response code
|
162 | headers - Object - the HTTP response headers
|
163 | config - Object - the `axios` config object used to make the request
|
164 | duration - Number - the number of milliseconds it took to run this request
|
165 | ```
|
166 |
|
167 | Sometimes on different platforms you need access to the original axios error
|
168 | that was thrown:
|
169 |
|
170 | ```
|
171 | originalError - Error - the error that axios threw in case you need more info
|
172 | ```
|
173 |
|
174 | ## Changing Base URL
|
175 |
|
176 | You can change the URL your api is connecting to.
|
177 |
|
178 | ```js
|
179 | api.setBaseURL('https://some.other.place.com/api/v100')
|
180 | console.log(`omg i am now at ${api.getBaseURL()}`)
|
181 | ```
|
182 |
|
183 | ## Changing Headers
|
184 |
|
185 | Once you've created your api, you're able to change HTTP requests by
|
186 | calling `setHeader` or `setHeaders` on the api. These stay with the api instance, so you can just set ['em and forget 'em](https://gitter.im/infinitered/ignite?at=582e57563f3946057acd2f84).
|
187 |
|
188 | ```js
|
189 | api.setHeader('Authorization', 'the new token goes here')
|
190 | api.setHeaders({
|
191 | Authorization: 'token',
|
192 | 'X-Even-More': 'hawtness',
|
193 | })
|
194 | ```
|
195 |
|
196 | ## Adding Monitors
|
197 |
|
198 | Monitors are functions you can attach to the API which will be called
|
199 | when any request is made. You can use it to do things like:
|
200 |
|
201 | - check for headers and record values
|
202 | - determine if you need to trigger other parts of your code
|
203 | - measure performance of API calls
|
204 | - perform logging
|
205 |
|
206 | Monitors are run just before the promise is resolved. You get an
|
207 | early sneak peak at what will come back.
|
208 |
|
209 | You cannot change anything, just look.
|
210 |
|
211 | Here's a sample monitor:
|
212 |
|
213 | ```js
|
214 | const naviMonitor = response => console.log('hey! listen! ', response)
|
215 | api.addMonitor(naviMonitor)
|
216 | ```
|
217 |
|
218 | Any exceptions that you trigger in your monitor will not affect the flow
|
219 | of the api request.
|
220 |
|
221 | ```js
|
222 | api.addMonitor(response => this.kaboom())
|
223 | ```
|
224 |
|
225 | Internally, each monitor callback is surrounded by an oppressive `try/catch`
|
226 | block.
|
227 |
|
228 | Remember. Safety first!
|
229 |
|
230 | ## Adding Transforms
|
231 |
|
232 | In addition to monitoring, you can change every request or response globally.
|
233 |
|
234 | This can be useful if you would like to:
|
235 |
|
236 | - fix an api response
|
237 | - add/edit/delete query string variables for all requests
|
238 | - change outbound headers without changing everywhere in your app
|
239 |
|
240 | Unlike monitors, exceptions are not swallowed. They will bring down the stack, so careful!
|
241 |
|
242 | ### Response Transforms
|
243 |
|
244 | For responses, you're provided an object with these properties.
|
245 |
|
246 | - `data` - the object originally from the server that you might wanna mess with
|
247 | - `duration` - the number of milliseconds
|
248 | - `problem` - the problem code (see the bottom for the list)
|
249 | - `ok` - true or false
|
250 | - `status` - the HTTP status code
|
251 | - `headers` - the HTTP response headers
|
252 | - `config` - the underlying axios config for the request
|
253 |
|
254 | Data is the only option changeable.
|
255 |
|
256 | ```js
|
257 | api.addResponseTransform(response => {
|
258 | const badluck = Math.floor(Math.random() * 10) === 0
|
259 | if (badluck) {
|
260 | // just mutate the data to what you want.
|
261 | response.data.doorsOpen = false
|
262 | response.data.message = 'I cannot let you do that.'
|
263 | }
|
264 | })
|
265 | ```
|
266 |
|
267 | Or make it async:
|
268 |
|
269 | ```js
|
270 | api.addAsyncResponseTransform(async response => {
|
271 | const something = await AsyncStorage.load('something')
|
272 | if (something) {
|
273 | // just mutate the data to what you want.
|
274 | response.data.doorsOpen = false
|
275 | response.data.message = 'I cannot let you do that.'
|
276 | }
|
277 | })
|
278 | ```
|
279 |
|
280 | ### Request Transforms
|
281 |
|
282 | For requests, you are given a `request` object. Mutate anything in here to change anything about the request.
|
283 |
|
284 | The object passed in has these properties:
|
285 |
|
286 | - `data` - the object being passed up to the server
|
287 | - `method` - the HTTP verb
|
288 | - `url` - the url we're hitting
|
289 | - `headers` - the request headers
|
290 | - `params` - the request params for `get`, `delete`, `head`, `link`, `unlink`
|
291 |
|
292 | Request transforms can be a function:
|
293 |
|
294 | ```js
|
295 | api.addRequestTransform(request => {
|
296 | request.headers['X-Request-Transform'] = 'Changing Stuff!'
|
297 | request.params['page'] = 42
|
298 | delete request.params.secure
|
299 | request.url = request.url.replace(/\/v1\//, '/v2/')
|
300 | if (request.data.password && request.data.password === 'password') {
|
301 | request.data.username = `${request.data.username} is secure!`
|
302 | }
|
303 | })
|
304 | ```
|
305 |
|
306 | And you can also add an async version for use with Promises or `async/await`. When you resolve
|
307 | your promise, ensure you pass the request along.
|
308 |
|
309 | ```js
|
310 | api.addAsyncRequestTransform(request => {
|
311 | return new Promise(resolve => setTimeout(resolve, 2000))
|
312 | })
|
313 | ```
|
314 |
|
315 | ```js
|
316 | api.addAsyncRequestTransform(request => async () => {
|
317 | await AsyncStorage.load('something')
|
318 | })
|
319 | ```
|
320 |
|
321 | This is great if you need to fetch an API key from storage for example.
|
322 |
|
323 | Multiple async transforms will be run one at a time in succession, not parallel.
|
324 |
|
325 | # Using Async/Await
|
326 |
|
327 | If you're more of a `stage-0` kinda person, you can use it like this:
|
328 |
|
329 | ```js
|
330 | const api = create({ baseURL: '...' })
|
331 | const response = await api.get('/slowest/site/on/the/net')
|
332 | console.log(response.ok) // yay!
|
333 | ```
|
334 |
|
335 | # Cancel Request
|
336 |
|
337 | ```js
|
338 | import { CancelToken } from 'apisauce'
|
339 |
|
340 | const source = CancelToken.source()
|
341 | const api = create({ baseURL: 'github.com' })
|
342 | api.get('/users', {}, { cancelToken: source.token })
|
343 |
|
344 | // To cancel request
|
345 | source.cancel()
|
346 | ```
|
347 |
|
348 | # Problem Codes
|
349 |
|
350 | The `problem` property on responses is filled with the best
|
351 | guess on where the problem lies. You can use a switch to
|
352 | check the problem. The values are exposed as `CONSTANTS`
|
353 | hanging on your built API.
|
354 |
|
355 | ```
|
356 | Constant VALUE Status Code Explanation
|
357 | ----------------------------------------------------------------------------------------
|
358 | NONE null 200-299 No problems.
|
359 | CLIENT_ERROR 'CLIENT_ERROR' 400-499 Any non-specific 400 series error.
|
360 | SERVER_ERROR 'SERVER_ERROR' 500-599 Any 500 series error.
|
361 | TIMEOUT_ERROR 'TIMEOUT_ERROR' --- Server didn't respond in time.
|
362 | CONNECTION_ERROR 'CONNECTION_ERROR' --- Server not available, bad dns.
|
363 | NETWORK_ERROR 'NETWORK_ERROR' --- Network not available.
|
364 | CANCEL_ERROR 'CANCEL_ERROR' --- Request has been cancelled. Only possible if `cancelToken` is provided in config, see axios `Cancellation`.
|
365 | ```
|
366 |
|
367 | Which problem is chosen will be picked by walking down the list.
|
368 |
|
369 | # Contributing
|
370 |
|
371 | Bugs? Comments? Features? PRs and Issues happily welcomed! Make sure to check out our [contributing guide](./github/CONTRIBUTING.md) to get started!
|