@nuskin/http-client

3.0.2 • Public • Published

HTTP-client

Make ajax calls through a unified interface.

Features


Installation:

$ npm i @nuskin/http-client or yarn add @nuskin/http-client

Usage:

The default import provides a pseudo-instance of HTTPClient with the convenience of being a function which behaves much in the same way as $.ajax, axios, or fetch. Under the hood it is delegating to its request method.

import api from '@nuskin/http-client';

api({
  url: '/foo',
  method: 'GET'
}).then(({ body }) => {
  console.log('body is JSON');
}).catch(error => {
  console.error(error);
  console.log(error.response.status);
});

api.post({
  url: '/foo',
  body: {
    foo: 'bar'
  }
});

You may also instantiate a new instance of HTTPClient via the create factory function. Instances have isolated interceptors (except of course for the global interceptors), and may be provided a base request-configuration which will get extended via shallow-merge with the configuration settings of each request being made. This allows you to provide any common settings which a group of calls may need to share.

import { create } from '@nuskin/http-client';

const api = create({
  baseConfig: {
    headers: {
      foo: 'bar'
    }
  }
});

// All calls will be sent with a 'foo: bar' header
api({ url: '/foo', method: 'GET' });
api.get({ url: '/foo' });

Interceptors:

Interceptors are functions which may run either globally or on an instance-level, and "intercept" an http call at different periods of the request. The stages are:

  • request
  • success
  • error

They may be thought of as "pipes" which receive input, and whose output will be used as the argument for the next interceptor or for the final request consumers in the application-code. To add a global interceptor, explicit methods to do so are available as named exports from the http-client library. Note that global interceptors are run before http-client interceptors, even if added after.

import api, { addGlobalInterceptor, removeGlobalInterceptor } from '@nuskin/http-client';

api.addInterceptor({
  request (config) {
    return Object.assign(config, {
      headers: {
        ...config.headers,
        'Client-Id': '12345'
      }
    }):
  },
  success (response) {
    return Object.assign(response, {
      customProp: 'applied-last'
    });
  }
});

addGlobalInterceptor({
  success (response) {
    return Object.assign(response, {
      customProp: 'applied-first'
    });
  }
});

const request = api.get('/foo');
request.headers['Client-Id'] === '12345';
request.then(response => {
  response.customProp === 'applied-last';
});

api.addInterceptor({
  error (error) {
    return 'foo';
  }
});

api.get('/foo-error')
  .then(response => {
    response === 'foo'; 
  })
  .catch(error => {
    // This won't get called since we're changing the error response into a plain object.
  });

// To remove an interceptor, a reference to the interceptor added must be used.

Testing:

Note: The following example uses jest, but mocking http-client is test-runner agnostic.

import { mock, mockNetworkError, mockTimeout } from '@nuskin/http-client';
import { showButtonOnSuccessfulAjaxCall } from '../myModule';

mock({
  '/get-button-contents': {
    GET: {
      status: 200,
      body: {
        bar: 'baz'
      }
    }
  }
});

...

it('Should succeed when calling foo, with data', async () => {
  await showButtonOnSuccessfulAjaxCall();
  expect(buttonElem.exists()).toBe(true);
});

API:

The request config

property type required default
url String
method String 'GET'
body Object
headers Object {'Accept': 'application/json', 'Content-Type': 'application/json'}
timeout Number 0
withCredentials Boolean

NOTE: additional options supplied in the request config are not supported by http-client, but are passed through to the makeRequest implementation.

Resolved object

property type info
status Number
body Object
config Object The request config used to make the call.
headers Object

Rejected object

The rejected object is an instance of a HTTPError. Errors resulting from a 404, 500, 403 will be instances of NotFoundError, InternalServerError, and ForbiddenError, respectively. The names of these Error sub-classes can be found here. To use the error classes, import them like so: import { NetworkError, NotFoundError } from '@nuskin/http-client';

property type info
message String An error description
response Object The server response
config Object The config used to make the request

Canceling

Currently, a request may be canceled using its cancel method:

const request = api.get('/foo');
request.cancel();

Advanced usage

HTTP-client's aim is to provide a well-defined interface over-top of an implementation which actually performs the request.

To use your own implementation, use the implementation option when using the create method.

To implement the http-client "back-end", all that is currently needed is to implement a makeRequest method. Here is an example which swaps the default implementation with a mock. This is similar to axios's adapters.

import { create } from '@nuskin/http-client';

const api = create({
  implementation: {
    makeRequest (config) {
      if (config.url === '/successMe') {
        return Promise.resolve({
          body: 'yay!',
          status: 200
        });
      } else {
        return Promise.reject({
          message: 'An error occurred',
          isError: true,
          response: {
            status: 500
          }
        });
      }
    }
  }
});

api.get('/successMe');

Testing

Testing of http-client is done through the provided testing-tools. It has a simple interface for setting responses. Calling mock multiple times will override the previous set of responses.

import { mock } from '@nuskin/http-client';

mock({
  '/test': {
    GET: {
      status: 200,
      body: {
        foo: 'bar!'
      },
      headers: {
        'X-FOO': 'BAR!'
      }
    },
    POST: {
      status: 500
    }
  },
  '/network-error': {
    GET: false,
  }
});

TODO

Caching

One of the main feature requests is to have a caching system handled by http-client. It was originally built with a semi-naive cache which handled cache expiration and pass-through calls, but lacked design and was implemented before interceptors which caused unintended complications in the code.

Some questions to consider:

  1. Are cached requests subject to new request interceptors which are added after the requests have been cached?
  2. Use Cache-Control request headers to implement caching?
  3. What should the default expiration be?
  4. How would the implementation differ if designing for a SPA or designing for a multi-page app?

Cancel token

The "Cancel Token" is something which has a similar implementation in fetch and would need to be implemented. Currently, this is done by adding it to the returned pending-request object. Perhaps this could be separated out and the implementation defined similarly to the "makeRequest" method, but more thought is needed on it.

Readme

Keywords

none

Package Sidebar

Install

npm i @nuskin/http-client

Weekly Downloads

38

Version

3.0.2

License

ISC

Unpacked Size

100 kB

Total Files

25

Last publish

Collaborators

  • rellenberger
  • klau
  • nkranendonk
  • emoore
  • nuskin-cws