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

@ns3/fetch-client

v5.1.0

Published

A light weight `FetchClient` and utilities for web and node (min v18)

Downloads

275

Readme

@ns3/fetch-client

fetch utilities for web and node (min v18).

Helper Functions

import { assertOk, toJson } from '@ns3/fetch-client';

// Ensures status ok and returns Promise<void>
fetch('https://google.com').then(assertOk);

// Ensures status ok and returns parsed body Promise<any>
fetch('https://example-api.com').then(toJson());

In case response.ok !== true those helpers will throw FetchError that you can type check against:

import { assertOk, FetchError } from '@ns3/fetch-client';

fetch('https://google.com')
  .then(assertOk)
  .catch((error) => {
    if (error instanceof FetchError) {
      console.log(error.response.status);
    }
  });

Interceptors

RequestInterceptor is a simple function that receives:

  • req - instance of fetch Request
  • next - method to call a next interceptor or a final fetch.

and returns a fetch Response.

You can modify Request before a call to a next function.

You can modify Response after a call to a next function.

You can use interceptors by creating a new instance of fetch function using interceptFetch function. It takes an array of interceptors as a first argument and optionally base fetch function as a second. It returns modified, interceptable fetch function.

import { interceptFetch, RequestInterceptor } from '@ns3/fetch-client';

const firstInterceptor: RequestInterceptor = async (req, next) => {
  console.log('first req');
  const result = await next(req);
  console.log('first res');
  return result;
};

const interceptableFetch = interceptFetch([firstInterceptor]);

Example

Here is a practical example of RequestInterceptor used for providing authorization header:

import { interceptFetch, RequestInterceptor } from '@ns3/fetch-client';

const authInterceptor: RequestInterceptor = async (req, next) => {
  // modify request by adding auth header
  const token = 'auth token from somewhere';
  req.headers.set('authorization', `Bearer ${token}`);

  const res = await next(req);

  // check response and decide what to do
  if (res.status === 401) {
    // maybe I should refresh token and try again
  } else {
    return res;
  }
};

const interceptableFetch = interceptFetch([authInterceptor]);

interceptableFetch('https://my-site-that-requires-auth.com').then(console.log);

Call Order

Having interceptors:

import { interceptFetch } from '@ns3/fetch-client';
import { firstInterceptor, secondInterceptor } from './somewhere';

const interceptableFetch = interceptFetch([firstInterceptor, secondInterceptor]);

The call order would be:

  • first req
  • second req
  • actual fetch
  • second res
  • first res

Extend

It is possible to extend existing interceptable fetch:

import { interceptFetch } from '@ns3/fetch-client';
import { extendInterceptor, firstInterceptor, secondInterceptor } from './somewhere';

const interceptableFetch = interceptFetch([firstInterceptor, secondInterceptor]);
const extendedFetch = interceptFetch([extendInterceptor], interceptableFetch);

The call order of extendedFetch would be:

  • extendInterceptor req
  • first req
  • second req
  • actual fetch
  • second res
  • first res
  • extendInterceptor res

Built-in interceptors

Library offers some built-in interceptors to address common use cases.

Timeout

To add timeout to a fetch call you can use timeoutInterceptor:

import { interceptFetch, timeoutInterceptor } from '@ns3/fetch-client';

const interceptableFetch1 = interceptFetch([timeoutInterceptor()]); // default 5000ms
const interceptableFetch2 = interceptFetch([timeoutInterceptor({ timeout: 1000 })]);

It will return response with status 408 if timeout is reached.

Retry

To add retry to a fetch call you can use retryInterceptor:

import { interceptFetch, retryInterceptor } from '@ns3/fetch-client';

// default 2 retries (3 calls in total) with scalling duration 1500ms, exclude 4xx responses
const interceptableFetch1 = interceptFetch([retryInterceptor()]);
const interceptableFetch2 = interceptFetch([
  retryInterceptor({
    maxRetryAttempts: 3, // 4 calls in total
    scalingDuration: 1000, // retries will be delayed respectively by [1000, 2000, 3000]
    excludePredicate: (res) => response.status < 500, // wont retry 4xx responses
  }),
]);

FetchClient

A light weight FetchClient. It was inspired by Angular's HttpClient and axios. It features:

  • Fetch API and all its features.
  • An object-oriented, Promise based API.
  • Request transformation (JSON.stringify + 'Content-Type': 'application/json' header).
  • Response transformation via toJson utility function.
  • baseUrl support.
  • headers support.
  • Interceptors via interceptFetch.

Base usage

import { FetchClient, toJson } from '@ns3/fetch-client';

const client = new FetchClient({
  baseUrl: 'https://example-api.com',
  headers: { 'x-api-key': '123' },
});

client.get('/retrieve').then(console.log);

// Body is `JSON.stringify`
// 'Content-Type': 'application/json' header is added
client.post('/upsert', { foo: 'bar' }).then(toJson()).then(console.log);
// Response is transformed using toJson function

Interceptors

FetchClient is made to work with RequestInterceptors. Constructor receives a fetch function as an optional first argument. You can use interceptFetch to provide it a fetch instance interceptable with RequestInterceptors.

import { FetchClient, interceptFetch } from '@ns3/fetch-client';
import { firstInterceptor, secondInterceptor } from './somewhere';

const client = new FetchClient({ fetch: interceptFetch([firstInterceptor, secondInterceptor]) });

With @ns3/di

For those using @ns3/di this is an example of simple integration with dependency injection system:

import { Container, Injectable } from '@ns3/di';
import {
  Fetch,
  FetchClient,
  interceptFetch,
  RequestHandler,
  retryInterceptor,
} from '@ns3/fetch-client';

@Injectable({ useValue: fetch })
export abstract class IFetch {}
export interface IFetch extends Fetch {}

@Injectable()
export class AppFetchClient extends FetchClient {
  constructor(_fetch: IFetch) {
    super(interceptFetch([retryInterceptor()], _fetch));
  }
}

const container = Container.make();
const client = container.get(AppFetchClient);

This way you can easily mock IFetch in your tests while still maintain convenient default Fetch value through IFetch symbol.