UNPKG

10.7 kBMarkdownView Raw
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
14Talking 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
40import { create } from 'apisauce'
41
42// define the api
43const api = create({
44 baseURL: 'https://api.github.com',
45 headers: { Accept: 'application/vnd.github.v3+json' },
46})
47
48// start making calls
49api
50 .get('/repos/skellock/apisauce/commits')
51 .then(response => response.data[0].commit.message)
52 .then(console.log)
53
54// customizing headers per-request
55api.post('/users', { name: 'steve' }, { headers: { 'x-gigawatts': '1.21' } })
56```
57
58See the examples folder for more code.
59
60# Documentation
61
62## Create an API
63
64You create an api by calling `.create()` and passing in a configuration object.
65
66```js
67const api = create({ baseURL: 'https://api.github.com' })
68```
69
70The only required property is `baseURL` and it should be the starting point for
71your API. It can contain a sub-path and a port as well.
72
73```js
74const api = create({ baseURL: 'https://example.com/api/v3' })
75```
76
77HTTP request headers for all requests can be included as well.
78
79```js
80const api = create({
81 baseURL: '...',
82 headers: {
83 'X-API-KEY': '123',
84 'X-MARKS-THE-SPOT': 'yarrrrr',
85 },
86})
87```
88
89Default timeouts can be applied too:
90
91```js
92const api = create({ baseURL: '...', timeout: 30000 }) // 30 seconds
93```
94
95You can also pass an already created axios instance
96
97```js
98import axios from 'axios'
99import { create } from 'apisauce'
100
101const customAxiosInstance = axios.create({ baseURL: 'https://example.com/api/v3' })
102
103const apisauceInstance = create({ axiosInstance: customAxiosInstance })
104```
105
106## Calling The API
107
108With your fresh `api`, you can now call it like this:
109
110```js
111api.get('/repos/skellock/apisauce/commits')
112api.head('/me')
113api.delete('/users/69')
114api.post('/todos', { note: 'jump around' }, { headers: { 'x-ray': 'machine' } })
115api.patch('/servers/1', { live: false })
116api.put('/servers/1', { live: true })
117api.link('/images/my_dog.jpg', {}, { headers: { Link: '<http://example.com/profiles/joe>; rel="tag"' } })
118api.unlink('/images/my_dog.jpg', {}, { headers: { Link: '<http://example.com/profiles/joe>; rel="tag"' } })
119api.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
140The responses are promise-based, so you'll need to handle things in a
141`.then()` function.
142
143The promised is always resolved with a `response` object.
144
145Even if there was a problem with the request! This is one of the goals of
146this library. It ensures sane calling code without having to handle `.catch`
147and have 2 separate flows.
148
149A response will always have these 2 properties:
150
151```
152ok - Boolean - True if the status code is in the 200's; false otherwise.
153problem - String - One of 6 different values (see below - problem codes)
154```
155
156If the request made it to the server and got a response of any kind, response
157will also have these properties:
158
159```
160data - Object - this is probably the thing you're after.
161status - Number - the HTTP response code
162headers - Object - the HTTP response headers
163config - Object - the `axios` config object used to make the request
164duration - Number - the number of milliseconds it took to run this request
165```
166
167Sometimes on different platforms you need access to the original axios error
168that was thrown:
169
170```
171originalError - Error - the error that axios threw in case you need more info
172```
173
174## Changing Base URL
175
176You can change the URL your api is connecting to.
177
178```js
179api.setBaseURL('https://some.other.place.com/api/v100')
180console.log(`omg i am now at ${api.getBaseURL()}`)
181```
182
183## Changing Headers
184
185Once you've created your api, you're able to change HTTP requests by
186calling `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
189api.setHeader('Authorization', 'the new token goes here')
190api.setHeaders({
191 Authorization: 'token',
192 'X-Even-More': 'hawtness',
193})
194```
195
196## Adding Monitors
197
198Monitors are functions you can attach to the API which will be called
199when 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
206Monitors are run just before the promise is resolved. You get an
207early sneak peak at what will come back.
208
209You cannot change anything, just look.
210
211Here's a sample monitor:
212
213```js
214const naviMonitor = response => console.log('hey! listen! ', response)
215api.addMonitor(naviMonitor)
216```
217
218Any exceptions that you trigger in your monitor will not affect the flow
219of the api request.
220
221```js
222api.addMonitor(response => this.kaboom())
223```
224
225Internally, each monitor callback is surrounded by an oppressive `try/catch`
226block.
227
228Remember. Safety first!
229
230## Adding Transforms
231
232In addition to monitoring, you can change every request or response globally.
233
234This 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
240Unlike monitors, exceptions are not swallowed. They will bring down the stack, so careful!
241
242### Response Transforms
243
244For 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
254Data is the only option changeable.
255
256```js
257api.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
267Or make it async:
268
269```js
270api.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
282For requests, you are given a `request` object. Mutate anything in here to change anything about the request.
283
284The 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
292Request transforms can be a function:
293
294```js
295api.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
306And you can also add an async version for use with Promises or `async/await`. When you resolve
307your promise, ensure you pass the request along.
308
309```js
310api.addAsyncRequestTransform(request => {
311 return new Promise(resolve => setTimeout(resolve, 2000))
312})
313```
314
315```js
316api.addAsyncRequestTransform(request => async () => {
317 await AsyncStorage.load('something')
318})
319```
320
321This is great if you need to fetch an API key from storage for example.
322
323Multiple async transforms will be run one at a time in succession, not parallel.
324
325# Using Async/Await
326
327If you're more of a `stage-0` kinda person, you can use it like this:
328
329```js
330const api = create({ baseURL: '...' })
331const response = await api.get('/slowest/site/on/the/net')
332console.log(response.ok) // yay!
333```
334
335# Cancel Request
336
337```js
338import { CancelToken } from 'apisauce'
339
340const source = CancelToken.source()
341const api = create({ baseURL: 'github.com' })
342api.get('/users', {}, { cancelToken: source.token })
343
344// To cancel request
345source.cancel()
346```
347
348# Problem Codes
349
350The `problem` property on responses is filled with the best
351guess on where the problem lies. You can use a switch to
352check the problem. The values are exposed as `CONSTANTS`
353hanging on your built API.
354
355```
356Constant VALUE Status Code Explanation
357----------------------------------------------------------------------------------------
358NONE null 200-299 No problems.
359CLIENT_ERROR 'CLIENT_ERROR' 400-499 Any non-specific 400 series error.
360SERVER_ERROR 'SERVER_ERROR' 500-599 Any 500 series error.
361TIMEOUT_ERROR 'TIMEOUT_ERROR' --- Server didn't respond in time.
362CONNECTION_ERROR 'CONNECTION_ERROR' --- Server not available, bad dns.
363NETWORK_ERROR 'NETWORK_ERROR' --- Network not available.
364CANCEL_ERROR 'CANCEL_ERROR' --- Request has been cancelled. Only possible if `cancelToken` is provided in config, see axios `Cancellation`.
365```
366
367Which problem is chosen will be picked by walking down the list.
368
369# Contributing
370
371Bugs? Comments? Features? PRs and Issues happily welcomed! Make sure to check out our [contributing guide](./github/CONTRIBUTING.md) to get started!