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

flexhr

v2.1.2

Published

Flexible XHR library

Downloads

34

Readme

Build Status

FleXHR

Flexible XHR library

Overview

There are many JavaScript XHR libraries out there. The best and most popular ones are really good. But the good ones, while fully featured, sometimes have too many features. This library has a lot less features, but it makes a few assumptions about how we want our XHR library to work, and then allows us to easily override those assumptions.

Installation

Install from the NPM repository with NPM:

npm install --save-dev flexhr

or with Yarn:

yarn add --dev flexhr

Usage

This library is generally divided into two parts that are used in conjunction: making requests, and handling the responses. These parts can be used independently as well because they rely on standard interfaces like the one provided by the Response object.

Making requests

The library exports a few low-level functions named after the HTTP methods we want to use. The supported methods are 'GET', 'POST', 'PUT', 'PATCH', and 'DELETE'. To make a simple GET request, we call the GET function:

import {GET} from 'flexhr';

(await function () {
  const resp = await GET('/api/users');
})();

The return value is a promise that resolves to a standard Response object. Therefore, the resp variable in the above example is a Response object.

To specify parameters for the GET request, we can use the params option:

import {GET} from 'flexhr';

(async function () {
  const resp = await GET('/api/users', {params: {filter: 12}});
})();

The last example results in a GET request to /api/users?filter=12.

The following options can be used:

  • params: key-value pairs that are converted to a query string (default is none).
  • headers: key-value pairs of headers as a plain JavaScript object, or a standard Headers object (default is to not add any extra headers).
  • skipPlugins: an array of plugin IDs to omit from this request (more about plugins later).

To make a POST request, we use the POST function just like we did with GET requests:

import {POST} from 'flexhr';

(async function () {
  const resp = await POST('/api/users');
})();

To pass the parameters as JSON, we use the params option like we did with GET:

import {POST} from 'flexhr';

(async function () {
  const resp = await POST('/api/users', {params: {
    name: 'John Doe', 
    email: '[email protected]',
  }});
})();

You will notice that you do not have to say that you want it as JSON as that's the default. The Content-Type: application/json header is automatically added as well. For payloads other than objects and arrays, the payload is used as is. The headers must be set appropriately if you are using some exotic payload.

For FormData and UrlSearchParams object, no specific headers are added.

import {POST} from 'flexhr';

const formData = new FormData();
formData.append('name', 'John Doe');
formData.append('email', '[email protected]');

(async function () {
  const resp = await POST('/api/users', { params: formData });
})();

Creating request objects without making requests

It is possible to create a request object without actually making a request. This is done by passing the noFetch option.

import {GET} from 'flexhr';

let request = GET('/api/users', { noFetch: true })

Note that when using the noFetch function, no plugins are applied to the request objet.

Handling responses

Handling responses can be done in many ways, and this is why this part of the FleXHR functionality is provided separately. It is very opinionated about API will work in a certain way, which may or may not work for us.

When handling responses, we typically want to branch into success and failure paths, and may further want to know more about the reason of the failure. FleXHR allows us to separate the execution into different branches at varying levels of granularity depending on how much we care.

At the very coarse spectrum, we will only have success and failure conditions (which we call OK and Error). On top of this, we can handle any number of specific HTTP status codes which will take precedence over generic OK and Error conditions. For instance, 200 or 204 take precedence over OK while 404 and 500 take precedence over Error branch.

All of this is facilitated through the handleResponse() function.

import {handleResponse, GET} from 'flexhr'

(async function () {
  const resp = await GET('/api/users/1');
  const result = await handleResponse(resp, {
    onOK: function (user) {
      console.log('We got a user with name ' + user.name);
      return user;
    },
    on404: function (errors) {
      console.log('Could not find a user with this ID');
      return null;
    },
    on0: function (errors) {
      console.log('Could not connect');
      return null;
    },
    onError: function (errors) {
      console.log('Hmm, something else happened');
      return null;
    },
  });
})();

As can be seen, various branches are clearly separated and we have a good overview of what the result of each branch might be. Every branch starts with on prefix followed by either OK or Error or a valid HTTP status code, or a 0 (zero).

Zero is special, as it is a made-up status code for when the connection to the server cannot be established. This would normally throw (reject) with fetch(), but here it is treated as yet another branch so it can be handled in a uniform way.

The OK branch will catch any status codes that are not specifically handled and are in the 200 to 299 range (inclusive). All other status codes, including zero, are caught by Error branch if not specifically handled.

Request plugins

Some behaviors can be defined globally and then applied to all request made in the application. Examples of such behaviors include prefixing all URls with a common prefix (e.g., '/api'), adding authentication headers too all requests, processing all response payloads (e.g., custom deserialization, or retrieving the contents of a data key), and so on.

Global behaviors are defined using plugins.

Plugins are function decorators (functions that take a function and return a function with modified behavior). They receive a function that behaves like the browser's fetch() function, and is expected to return a function that has a similar behavior: it must take a URL and an init object (it doesn't need to handle the case where only a Request object is passed). The URL is a string, and the init object is a subset of the request init object (see here) which includes the keys: method, headers and body. The headers key is always a Headers object.

Here is an example that appends the '/api' prefix to all URLs:

function apiURLPlugin(next) {
  return function (url, init) {
    url = '/api' + url;
    return next(url, init);
  };
}

To add a plugin, we call the addPlugin() function:

import {addPlugin} from 'flexhr';

addPlugin(apiURLPlugin);

Plugins can be skipped on per-request basis. To skip a plugin, the plugin must first be given an ID. This is achieved by assigning to an id property on the plugin decorator:

apiURLPlugin.id = 'apiURLPlugin';

The id can be anything as long as it is a valid JavaScript value that can be compared with using the equality operator ===. We usually use strings with (to us) meaningful names.

Now to make a request skip a plugin, we use the skipPlugin option:

import {GET} from 'flexhr';

(async function () {
  const resp = await GET('/auth/users', {skipPlugins: ['apiURLPlugin']}); 
})();

The skipPlugins array contains plugin ID's which can be in any order. Plugins whose ID's are found in this array.