npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@nuskin/http-client

v3.0.2

Published

Http-client with a well-defined interface, swappable implementation, and in-built testing-tools.

Downloads

84

Readme

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.