•  


GitHub - infinitered/apisauce: Axios + standardized errors + request/response transforms.
Skip to content

Axios + standardized errors + request/response transforms.

License

Notifications You must be signed in to change notification settings

infinitered/apisauce

Repository files navigation

Apisauce

(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.

npm module

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 get called each request
  • attach functions that change all request or response data
  • detects connection issues (on React Native)

Installing

npm i apisauce --save or yarn add apisauce

  • Depends on axios .
  • Compatible with ES5.
  • Built with TypeScript.
  • Supports Node, the browser, and React Native.

Quick Start

// showLastCommitMessageForThisLibrary.js

import
 {
 create
 }
 from
 'apisauce'


// 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
 =>
 response
.
data
[
0
]
.
commit
.
message
)

  .
then
(
console
.
log
)


// customizing headers per-request

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

See the examples folder for more code.

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/api/v3'
 }
)

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

const
 api
 =
 create
(
{

  baseURL
: 
'...'
,

  headers
: 
{

    'X-API-KEY'
: 
'123'
,

    'X-MARKS-THE-SPOT'
: 
'yarrrrr'
,

  }
,

}
)

Default timeouts can be applied too:

const
 api
 =
 create
(
{
 baseURL
: 
'...'
,
 timeout
: 
30000
 }
)
 // 30 seconds

You can also pass an already created axios instance

import
 axios
 from
 'axios'

import
 {
 create
 }
 from
 'apisauce'


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


const
 apisauceInstance
 =
 create
(
{
 axiosInstance
: 
customAxiosInstance
 }
)

Calling The API

With your fresh api , 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 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)

any only accept one parameter

  • config - Object - config passed along to the axios request, this object same as axiosConfig

Responses

The responses are promise-based, so 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 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, 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

Sometimes on different platforms you need access to the original axios error that was thrown:

originalError - Error - the error that axios threw in case you need more info

Changing Base URL

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
(
)
}
`
)

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'
,

}
)

Adding Monitors

Monitors are functions you can attach to the API which will be called when 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 an early sneak peak at what will come back.

You cannot 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.

api
.
addMonitor
(
response
 =>
 this
.
kaboom
(
)
)

Internally, each monitor callback is surrounded by an oppressive try/catch block.

Remember. Safety first!

Adding Transforms

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!

Response Transforms

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

Data is the only option changeable.

api
.
addResponseTransform
(
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.'

  }

}
)

Or make it async:

api
.
addAsyncResponseTransform
(
async
 response
 =>
 {

  const
 something
 =
 await
 AsyncStorage
.
load
(
'something'
)

  if
 (
something
)
 {

    // just mutate the data to what you want.

    response
.
data
.
doorsOpen
 =
 false

    response
.
data
.
message
 =
 'I cannot let you do that.'

  }

}
)

Request Transforms

For requests, you are given a request 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 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

Request transforms can be a function:

api
.
addRequestTransform
(
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!`

  }

}
)

And you can also add an async version for use with Promises or async/await . When you resolve your promise, ensure you pass the request along.

api
.
addAsyncRequestTransform
(
request
 =>
 {

  return
 new
 Promise
(
resolve
 =>
 setTimeout
(
resolve
,
 2000
)
)

}
)
api
.
addAsyncRequestTransform
(
request
 =>
 async
 (
)
 =>
 {

  await
 AsyncStorage
.
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.

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!

Cancel Request

import
 {
 CancelToken
 }
 from
 'apisauce'


const
 source
 =
 CancelToken
.
source
(
)

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

api
.
get
(
'/users'
,
 {
}
,
 {
 cancelToken
: 
source
.
token
 }
)


// To cancel request

source
.
cancel
(
)

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.
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.

Mocking with axios-mock-adapter (or other libraries)

A common testing pattern is to use axios-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")
})

Contributing

Bugs? Comments? Features? PRs and Issues happily welcomed! Make sure to check out our contributing guide to get started!

- "漢字路" 한글한자자동변환 서비스는 교육부 고전문헌국역지원사업의 지원으로 구축되었습니다.
- "漢字路" 한글한자자동변환 서비스는 전통문화연구회 "울산대학교한국어처리연구실 옥철영(IT융합전공)교수팀"에서 개발한 한글한자자동변환기를 바탕하여 지속적으로 공동 연구 개발하고 있는 서비스입니다.
- 현재 고유명사(인명, 지명등)을 비롯한 여러 변환오류가 있으며 이를 해결하고자 많은 연구 개발을 진행하고자 하고 있습니다. 이를 인지하시고 다른 곳에서 인용시 한자 변환 결과를 한번 더 검토하시고 사용해 주시기 바랍니다.
- 변환오류 및 건의,문의사항은 juntong@juntong.or.kr로 메일로 보내주시면 감사하겠습니다. .
Copyright ⓒ 2020 By '전통문화연구회(傳統文化硏究會)' All Rights reserved.
 한국   대만   중국   일본