2.3.11 • Published 4 years ago

@rexlabs-spicerhaart/api-client v2.3.11

Weekly downloads
-
License
MIT
Repository
-
Last release
4 years ago

Api Client

Talking to APIs doesn't have to be awkward anymore.

Based originally on apisauce

Features

  • 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 intercept request's or response's
  • attach functions that get called each response
  • detects connection issues

Caveats

  • No support (adapter) for fetch, so no precache via service workers

Installing

npm i --save @rexlabs/api-client
yarn add @rexlabs/api-client

  • Depends on axios 0.11.1+.
  • Targets ES5.
  • Built with ES6.
  • Supported in Node and the browser(s).

Quick Start

// showLastCommitMessageForThisLibrary.js
import { create } from '@rexlabs/api-client';

// define the api
const api = create({
  baseURL: 'https://api.github.com',
  headers: {'Accept': 'application/vnd.github.v3+json'}
});

// start making calls
api
  .get('/repos/skellock/apisauce/commits')
  .then((response) => console.log(response.data[0].commit.message))

// respond to api errors (check `problem` instead of catching runtime errors)
api
  .get('/repos/skellock/apisauce/commits')
  .then((response) => {
    if (response.problem) {
      console.log(response.problem)
    }
  })

// customizing headers per-request
api.post('/users', {name: 'steve'}, {headers: {'x-gigawatts': '1.21'}});

Documentation

Create an API

You create an api by calling .create() and passing in a configuration object.

const api = create({baseURL: 'https://api.github.com'});

The only required property is baseURL and it should be the starting point for your API. It can contain a sub-path and a port as well.

const api = create({baseURL: 'https://example.com:8000/api/v3'});

HTTP request headers for all requests can be included as well.

const api = create({
  baseURL: 'https://api.github.com',
  headers: {
    'X-API-KEY': '123',
    'X-MARKS-THE-SPOT': 'yarrrrr'
  }
});

Configuration

PropertyTypeDescription
baseURLStringPrepended to url unless url is absolute.
headersObjectCustom headers to be sent.
timeoutNumberMilliseconds before the request times out.
withCredentialsBooleanIndicates whether or not cross-site Access-Control requests should be made using credentials.
responseTypeStringType of data that the server will respond with options are 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'.
xsrfCookieNameStringName of the cookie to use as a value for xsrf token.
xsrfHeaderNameStringName of the http header that carries the xsrf token value.
maxContentLengthNumberMax size of the http response content allowed.
adapterconfigCustom handling of requests which makes testing easier. See axios adapter docs.
cancelTokenCancelTokenCancel token that can be used to cancel the request
transformRequest{data}|{data}[]Changes to the request data before it is sent to the server. Almost always opt for api-client API instead.
transformResponse{data}|{data}[]Changes to the response data before it is passed to then. Almost always opt for api-client API instead.
auth{username,password}HTTP Basic auth. Sets an Authorization header, overwriting any existing.
proxy{host,port,auth}Hostname, port and auth of the proxy server. Sets Proxy-Authorization header, overwriting any existing header.
paramsSerializer(params: any) => StringFunction to serialize params.
onUploadProgress(progressEvent: any) => voidHandling of progress events for uploads.
onDownloadProgress(progressEvent: any) => voidHandling of progress events for downloads.
validateStatus(status: Number) => BooleanResolve or reject the promise for a given HTTP response. Almost always opt for api-client API instead.
maxRedirectsNumber(nodejs) Maximum number of redirects to follow.
httpAgentObject(nodejs) New http.Agent.
httpsAgentObject(nodejs) New https.Agent.

Calling The API

With your fresh api, you can now call it like this:

api.get('/cool/rest/endpoint')
api.head('/me')
api.options('/ambiguous')
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.request({method: 'GET', headers: {'x-super': 'duper'}})

get, head, options, delete, link and unlink 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, and patch 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)

request accept 1 parameter (good for re-queueing failed requests):

  • axiosConfig - Object - config passed along to the axios request (required)

Responses

The responses are promise-based, so you you'll need to handle things in a .then() function.

The promised is always resolved with a response object.

Even if there was a problem with the request! This is one of the goals of this 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 is 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, response will also have these properties:

data     - Object - this is probably the thing you're after.
status   - Number - the HTTP response code
headers  - Object - the HTTP response headers
config   - Object - the `axios` config object used to make the request
duration - Number - the number of milliseconds it took to run this request

Changing Headers

Once you've created your api, you're able to change HTTP requests by calling setHeader or setHeaders 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'
})

Intercepting Requests & Responses

Every request or response can be changed globally. Interceptors are functions that are given the request or response payload, and are expected to return an object to replace the payload.

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
  • determine if you need to block while triggering other parts of your code

Exceptions are not swallowed. They will bring down the request stack, so careful!

Response Interceptors

For responses, you're provided an object with these properties.

  • data - the object originally from the server that you might wanna mess with
  • duration - the number of milliseconds
  • problem - the problem code (see the bottom for the list)
  • ok - true or false
  • status - the HTTP status code
  • headers - the HTTP response headers
  • config - the underlying axios config for the request
api.addResponseInterceptor(response => {
  const badluck = Math.floor(Math.random() * 10) === 0
  if (badluck) {
    // just mutate the data to what you want.
    response.data.doorsOpen = false
    response.data.message = 'I cannot let you do that.'
  }
  return response;
})

Request Interceptors

For requests, you're provided an object with these properties.

  • data - the object being passed up to the server
  • method - the HTTP verb
  • url - the url we're hitting
  • headers - the request headers
  • params - the request params for get, delete, head, link, unlink
  • ... any other properties you intended to pass to axios

Be careful not to lose extra axios properties in the returned object.

api.addRequestInterceptor(request => {
  request.headers['X-Request-Transform'] = 'Changing Stuff!'
  request.params['page'] = 42
  delete request.params.secure
  request.url = request.url.replace(/\/v1\//, '/v2/')
  if (request.data.password && request.data.password === 'password') {
    request.data.username = `${request.data.username} is secure!`
  }
  return request;
})

Adding Monitors

Monitors are functions you can attach to the API which will be called when any response is recieved. It is an internal response interceptor.

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 an early sneak peak at what will come back.

You shouldn't change anything, just look.

Here's a sample monitor:

const naviMonitor = (response) => console.log('hey!  listen! ', response);
api.addMonitor(naviMonitor);

Any exceptions that you trigger in your monitor will not affect the flow of the api request.

Cancelling Requests

Axios supports cancelling out of the box, so api-client does too!

const source = CancelToken.source();
const api = create({baseURL: '...'});
const response = await api.get('/slowest/site/on/the/net', {}, {cancelToken: source});
source.cancel();
console.log(response.problem); // CANCEL_ERROR

Using Async/Await

If you're more of a stage-0 kinda person, you can use it like this:

const api = create({baseURL: '...'});
const response = await api.get('/slowest/site/on/the/net');
console.log(response.ok); // yay!

Problem Codes

The problem property on responses is filled with the best guess on where the problem lies. You can use a switch to check the problem. The values are exposed as CONSTANTS hanging on your built API.

Constant        VALUE               Status Code   Explanation
----------------------------------------------------------------------------------------
NONE             null               200-299       No problems.
NETWORK_ERROR    'NETWORK_ERROR'    ---           Network not available.
CANCEL_ERROR     'CANCEL_ERROR'     ---           Request was cancelled manually.
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.
UNKNOWN_ERROR    'UNKNOWN_ERROR'    ---           Unknown? Find out why, then contribute back.

Which problem is chosen will be picked by walking down the list.

Release Notes

2.2.3 - April 5, 2017

  • Add jsdoc for create(config)
  • Add jsdoc for created api clients. eg client.get() gets param hinting in editors
  • Add jsdoc for created api clients. eg client.get() gets param hinting in editors
  • Add client.options() to created api clients
  • Bump axios major version
    • axios.options()
    • typescript def changed (*breaking change*)

2.1.0 - December 6, 2016

  • Add an new request method, api.request
  • Fix an issue that was causing runaway promise errors

2.0.0 - December 4, 2016

  • Replace Transforms with Interceptors
  • Remove unused dev dependencies

1.0.1 - December 4, 2016

  • Package rename from apisauce to api-sauce
  • Add yarn.lock (yay yarn)

1.0.0 - December 1, 2016

  • Fork to Rex Software
  • Support request cancellation
  • Remove dependencies:
    • Ava
    • Standard
    • Ramda(sauce)
  • Introduce dependencies:
    • Jest
    • Semistandard
    • Lodash (individual functions)

0.6.0 - November 2, 2016

0.5.0 - August 28, 2016

  • NEW Adds more options to addRequestTransform - @skellock (#28)
  • NOTE Due to how Axios stores headers and our new mutable transforms, I had to move header storage out of Axios and into Apisauce. This will only affect you if you're talking to the Axios object directly. I didn't really predict this coming, so heads up if you're talking to the Axios object currently. It's better to just ask me to change Apisauce to include the missing features. By the time we get to 1.0, we actually might not even use Axios anymore! =)

0.4.0 - August 17, 2016

  • NEW Adds transform support for request and response - @skellock (#26)
  • NEW Upgraded to axios 0.13 and fixed a few API changes to make it transparent - @skellock (#24)
  • FIX Exposes the config (request) object when the API call fails @skellock (#25)

0.3.0 - July 1st, 2016

  • NEW setHeader and setHeaders for updating HTTP request headers - @skellock

0.2.0 - July 1st, 2016

  • NEW Bumped all dependencies to the latest version.
  • NEW Network errors and timeouts are now detected on React Native - @skellock

0.1.5 - June 1st, 2016

  • FIX Fixed up the problematic babel references in package.json - @gantman

0.1.4 - May 31st, 2016

  • NEW Bumped all dependencies to latest version.
  • FIX Repaired dev dependencies. thx @gabceb

0.1.3 - April 18th, 2016

  • FIX Forgot to run the dist script to repackage. :( Failsauce.

0.1.2 - April 18th, 2016

  • NEW Added duration (in milliseconds) to the response.

0.1.1 - April 10th, 2016

  • NEW timeout detection

0.1.0 - April 10th, 2016

  • Initial Release

TODO

  • Detect network failures on iOS and Android.
2.3.11

4 years ago