- Notifications
You must be signed in to change notification settings - Fork187
Axios + standardized errors + request/response transforms.
License
infinitered/apisauce
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
(Ring ring ring)< Hello?> Hi, can I speak to JSON API.< Speaking.> Hi, it's me JavaScript. Look, we need to talk.< Now is not a good time...> Wait, I just wanted to say, sorry.< ...
Talking to APIs doesn't have to be awkward anymore.
- low-fat wrapper for the amazing
axios
http client library - all responses follow the same flow: success and failure alike
- responses have a
problem
property to help guide exception flow - attach functions that get called each request
- attach functions that change all request or response data
- detects connection issues (on React Native)
npm i apisauce --save
oryarn add apisauce
- Depends on
axios
. - Compatible with ES5.
- Built with TypeScript.
- Supports Node, the browser, and React Native.
// showLastCommitMessageForThisLibrary.jsimport{create}from'apisauce'// define the apiconstapi=create({baseURL:'https://api.github.com',headers:{Accept:'application/vnd.github.v3+json'},})// start making callsapi.get('/repos/skellock/apisauce/commits').then(response=>response.data[0].commit.message).then(console.log)// customizing headers per-requestapi.post('/users',{name:'steve'},{headers:{'x-gigawatts':'1.21'}})
See the examples folder for more code.
You create an api by calling.create()
and passing in a configuration object.
constapi=create({baseURL:'https://api.github.com'})
The only required property isbaseURL
and it should be the starting point foryour API. It can contain a sub-path and a port as well.
constapi=create({baseURL:'https://example.com/api/v3'})
HTTP request headers for all requests can be included as well.
constapi=create({baseURL:'...',headers:{'X-API-KEY':'123','X-MARKS-THE-SPOT':'yarrrrr',},})
Default timeouts can be applied too:
constapi=create({baseURL:'...',timeout:30000})// 30 seconds
You can also pass an already created axios instance
importaxiosfrom'axios'import{create}from'apisauce'constcustomAxiosInstance=axios.create({baseURL:'https://example.com/api/v3'})constapisauceInstance=create({axiosInstance:customAxiosInstance})
With your freshapi
, you can now call it like this:
api.get('/repos/skellock/apisauce/commits')api.head('/me')api.delete('/users/69')api.post('/todos',{note:'jump around'},{headers:{'x-ray':'machine'}})api.patch('/servers/1',{live:false})api.put('/servers/1',{live:true})api.link('/images/my_dog.jpg',{},{headers:{Link:'<http://example.com/profiles/joe>; rel="tag"'}})api.unlink('/images/my_dog.jpg',{},{headers:{Link:'<http://example.com/profiles/joe>; rel="tag"'}})api.any({method:'GET',url:'/product',params:{id:1}})
get
,head
,delete
,link
andunlink
accept 3 parameters:
- url - the relative path to the API (required)
- params - Object - query string variables (optional)
- axiosConfig - Object - config passed along to the
axios
request (optional)
post
,put
, andpatch
accept 3 different parameters:
- url - the relative path to the API (required)
- data - Object - the object jumping the wire
- axiosConfig - Object - config passed along to the
axios
request (optional)
any
only accept one parameter
- config - Object - config passed along to the
axios
request, this object same asaxiosConfig
The responses are promise-based, so you'll need to handle things in a.then()
function.
The promised is always resolved with aresponse
object.
Even if there was a problem with the request! This is one of the goals ofthis library. It ensures sane calling code without having to handle.catch
and have 2 separate flows.
A response will always have these 2 properties:
ok - Boolean - True if the status code is in the 200's; false otherwise.problem - String - One of 6 different values (see below - problem codes)
If the request made it to the server and got a response of any kind, responsewill also have these properties:
data - Object - this is probably the thing you're after.status - Number - the HTTP response codeheaders - Object - the HTTP response headersconfig - Object - the `axios` config object used to make the requestduration - Number - the number of milliseconds it took to run this request
Sometimes on different platforms you need access to the original axios errorthat was thrown:
originalError - Error - the error that axios threw in case you need more info
You can change the URL your api is connecting to.
api.setBaseURL('https://some.other.place.com/api/v100')console.log(`omg i am now at${api.getBaseURL()}`)
Once you've created your api, you're able to change HTTP requests bycallingsetHeader
orsetHeaders
on the api. These stay with the api instance, so you can just set'em and forget 'em.
api.setHeader('Authorization','the new token goes here')api.setHeaders({Authorization:'token','X-Even-More':'hawtness',})
Monitors are functions you can attach to the API which will be calledwhen any request is made. You can use it to do things like:
- check for headers and record values
- determine if you need to trigger other parts of your code
- measure performance of API calls
- perform logging
Monitors are run just before the promise is resolved. You get anearly sneak peak at what will come back.
You cannot change anything, just look.
Here's a sample monitor:
constnaviMonitor=response=>console.log('hey! listen! ',response)api.addMonitor(naviMonitor)
Any exceptions that you trigger in your monitor will not affect the flowof the api request.
api.addMonitor(response=>this.kaboom())
Internally, each monitor callback is surrounded by an oppressivetry/catch
block.
Remember. Safety first!
In addition to monitoring, you can change every request or response globally.
This can be useful if you would like to:
- fix an api response
- add/edit/delete query string variables for all requests
- change outbound headers without changing everywhere in your app
Unlike monitors, exceptions are not swallowed. They will bring down the stack, so be careful!
For responses, you're provided an object with these properties.
data
- the object originally from the server that you might wanna mess withduration
- the number of millisecondsproblem
- the problem code (see the bottom for the list)ok
- true or falsestatus
- the HTTP status codeheaders
- the HTTP response headersconfig
- the underlying axios config for the request
Data is the only option changeable.
api.addResponseTransform(response=>{constbadluck=Math.floor(Math.random()*10)===0if(badluck){// just mutate the data to what you want.response.data.doorsOpen=falseresponse.data.message='I cannot let you do that.'}})
Or make it async:
api.addAsyncResponseTransform(asyncresponse=>{constsomething=awaitAsyncStorage.load('something')if(something){// just mutate the data to what you want.response.data.doorsOpen=falseresponse.data.message='I cannot let you do that.'}})
For requests, you are given arequest
object. Mutate anything in here to change anything about the request.
The object passed in has these properties:
data
- the object being passed up to the servermethod
- the HTTP verburl
- the url we're hittingheaders
- the request headersparams
- the request params forget
,delete
,head
,link
,unlink
Request transforms can be a function:
api.addRequestTransform(request=>{request.headers['X-Request-Transform']='Changing Stuff!'request.params['page']=42deleterequest.params.securerequest.url=request.url.replace(/\/v1\//,'/v2/')if(request.data.password&&request.data.password==='password'){request.data.username=`${request.data.username} is secure!`}})
And you can also add an async version for use with Promises orasync/await
. When you resolveyour promise, ensure you pass the request along.
api.addAsyncRequestTransform(request=>{returnnewPromise(resolve=>setTimeout(resolve,2000))})
api.addAsyncRequestTransform(request=>async()=>{awaitAsyncStorage.load('something')})
This is great if you need to fetch an API key from storage for example.
Multiple async transforms will be run one at a time in succession, not parallel.
If you're more of astage-0
kinda person, you can use it like this:
constapi=create({baseURL:'...'})constresponse=awaitapi.get('/slowest/site/on/the/net')console.log(response.ok)// yay!
import{CancelToken}from'apisauce'constsource=CancelToken.source()constapi=create({baseURL:'github.com'})api.get('/users',{},{cancelToken:source.token})// To cancel requestsource.cancel()
Theproblem
property on responses is filled with the bestguess on where the problem lies. You can use a switch tocheck the problem. The values are exposed asCONSTANTS
hanging on your built API.
Constant VALUE Status Code Explanation----------------------------------------------------------------------------------------NONE null 200-299 No problems.CLIENT_ERROR 'CLIENT_ERROR' 400-499 Any non-specific 400 series error.SERVER_ERROR 'SERVER_ERROR' 500-599 Any 500 series error.TIMEOUT_ERROR 'TIMEOUT_ERROR' --- Server didn't respond in time.CONNECTION_ERROR 'CONNECTION_ERROR' --- Server not available, bad dns.NETWORK_ERROR 'NETWORK_ERROR' --- Network not available.CANCEL_ERROR 'CANCEL_ERROR' --- Request has been cancelled. Only possible if `cancelToken` is provided in config, see axios `Cancellation`.
Which problem is chosen will be picked by walking down the list.
A common testing pattern is to useaxios-mock-adapter
to mock axios and respond with stubbed data. These libraries mock a specific instance of axios, and don't globally intercept all instances of axios. When using a mocking library like this, it's important to make sure to pass the same axios instance into the mock adapter.
Here is an example code from axios_mock, modified to work with Apisauce:
import apisauce from 'apisauce'import MockAdapter from 'axios-mock-adapter'test('mock adapter', async () => { const api = apisauce.create("https://api.github.com")- const mock = new MockAdapter(axios)+ const mock = new MockAdapter(api.axiosInstance) mock.onGet("/repos/skellock/apisauce/commits").reply(200, { commits: [{ id: 1, sha: "aef849923444" }], }); const response = await api..get('/repos/skellock/apisauce/commits') expect(response.data[0].sha).toEqual"aef849923444")})
Bugs? Comments? Features? PRs and Issues happily welcomed! Make sure to check out ourcontributing guide to get started!
About
Axios + standardized errors + request/response transforms.