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

@jalik/fetch-client

v2.3.0

Published

Fetch wrapper to manage requests/responses more easier

Downloads

164

Readme

@jalik/fetch-client

GitHub package.json version Build Status Last commit GitHub issues GitHub npm

HTTP client based on Fetch API with error handling and other DX improvements.

Features

  • Based on Fetch API (RequestInit + Response), with extra options
  • Shortcut methods (DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT)
  • Global configuration for all requests (headers, options and base URL)
  • Conversion of response body using a type (json, blob, text, arrayBuffer...)
  • Transform request options and headers before sending
  • Transform response body before return
  • Transform response error before return
  • TypeScript friendly

Requires Fetch support in Browser or Node (>=18), use a polyfill to support other environments.

Sandbox

Play with the lib here: https://codesandbox.io/s/jalik-fetch-client-demo-8rolt2?file=/src/index.js

Creating a client

import { FetchClient } from '@jalik/fetch-client'

const client = new FetchClient()

Executing a request

The method .fetch(url, options) is a generic method to execute a request.
It's like calling fetch() directly, but with all the benefits of using FetchClient (error handling, body transformations...).
Usually, you would prefer to use a shortcut method (described after) like .get() or .post() instead of .fetch().

import { FetchClient } from '@jalik/fetch-client'

const client = new FetchClient()

client.fetch('https://jsonplaceholder.typicode.com/todos/1', {
  method: 'GET',
  responseType: 'json'
})
  .then((resp) => {
    console.log(resp.body)
  })

Request options

The request options are the same as Fetch options with extra options.

type FetchOptions = RequestInit & {
  /**
   * The type of response to expect.
   * Pass undefined to ignore response body.
   */
  responseType?: ResponseType
}

Response object

The response object returned by all request methods follows the declaration below.

type FetchClientResponse<T = any> = {
  /**
   * Response body.
   */
  body: T
  /**
   * Response headers.
   */
  headers: Record<string, string>
  /**
   * The original Fetch Response.
   */
  original: Response
  /**
   * Tells if the request has been redirected.
   */
  redirected: boolean
  /**
   * Response status code (ex: 200).
   */
  status: number
  /**
   * Response status text (ex: "OK").
   */
  statusText: string
  /**
   * Contains the response type.
   */
  type: globalThis.ResponseType
}

Executing a DELETE request

import { FetchClient } from '@jalik/fetch-client'

const client = new FetchClient()

client.delete('https://jsonplaceholder.typicode.com/posts/1')

Executing a GET request

import { FetchClient } from '@jalik/fetch-client'

const client = new FetchClient()

client.get('https://jsonplaceholder.typicode.com/todos/1', {
  // Convert response body to JSON.
  // It can be done per request, or for all requests when passed to FetchClient options.
  responseType: 'json',
})
  .then((resp) => {
    console.log(resp.body)
  })

Executing a HEAD request

import { FetchClient } from '@jalik/fetch-client'

const client = new FetchClient()

client.head('https://jsonplaceholder.typicode.com/todos/1')
  .then((resp) => {
    // Access response headers
    console.log(resp.headers)
  })

Executing an OPTIONS request

import { FetchClient } from '@jalik/fetch-client'

const client = new FetchClient()

client.options('https://jsonplaceholder.typicode.com/todos')
  .then((resp) => {
    // Access response headers
    console.log(resp.headers)
  })

Executing a PATCH request

When body is an object and Content-Type is not defined in headers:

  • body is serialized to JSON
  • Content-Type: application/json is added to headers
import { FetchClient } from '@jalik/fetch-client'

const client = new FetchClient()

client.patch(
  'https://jsonplaceholder.typicode.com/todos/1',
  { completed: true },
  { responseType: 'json' }
)
  .then((resp) => {
    console.log(resp.body)
  })

Executing a POST request

When body is an object and Content-Type is not defined in headers:

  • body is serialized to JSON
  • Content-Type: application/json is added to headers
import { FetchClient } from '@jalik/fetch-client'

const client = new FetchClient()

client.post(
  'https://jsonplaceholder.typicode.com/todos',
  { title: 'test' },
  { responseType: 'json' }
)
  .then((resp) => {
    console.log(resp.body)
  })

Executing a PUT request

When body is an object and Content-Type is not defined in headers:

  • body is serialized to JSON
  • Content-Type: application/json is added to headers
import { FetchClient } from '@jalik/fetch-client'

const client = new FetchClient()

client.put(
  'https://jsonplaceholder.typicode.com/todos/1',
  { title: 'test' },
  { responseType: 'json' }
)
  .then((resp) => {
    console.log(resp.body)
  })

Handling errors

When the server returns an error code (4xx, 5xx...), the client throws an error.
If the server returned a body (containing error details), it can be found in error.response.body.
However be aware that the body is only available when responseType is defined in FetchClient options or in request options.

import { FetchClient } from '@jalik/fetch-client'

const client = new FetchClient()

const invalidObject = {}

client.post('https://jsonplaceholder.typicode.com/todos', invalidObject, {
  // Setting the responseType is important to convert error response body.
  responseType: 'json',
})
  .catch((error: FetchResponseError) => {
    console.error(
      // the status error
      error.message,
      // the server response
      error.response.body
    )
  })

By default, the error contains a basic message (status text like "Bad Request"). You can use the error returned by the server like below (this will be applied to all client responses).

import { FetchClient } from '@jalik/fetch-client'

const client = new FetchClient({
  transformError: (error: FetchResponseError, response: FetchClientResponse) => {
    // Return custom server error.
    if (error.response.body?.message) {
      return new FetchResponseError(error.response.body.message, response)
    }
    return error
  },
})

client.post('https://jsonplaceholder.typicode.com/todos', invalidObject, {
  // Setting the responseType is important to convert error response body.
  responseType: 'json',
})
  .catch((error: FetchResponseError) => {
    // the error message has the same value as "error.response.body.error"
    console.error(error.message)
  })

Configuring the client

import { FetchClient } from '@jalik/fetch-client'

const client = new FetchClient({
  // Do something async after each successful request (200 >= code < 400).
  afterEach: async (url, resp) => {
    return resp
  },
  // Prefix all relative URL with the base URL (does nothing on absolute URL).
  baseUrl: 'http://localhost',
  // Do something async before each request.
  beforeEach: async (url, options) => {
    return options
  },
  // Set default headers for all requests (empty by default).
  headers: {
    'authorization': '...',
    'x-xsrf-token': '...',
  },
  // Set default Fetch options for all requests.
  options: {
    mode: 'cors',
  },
  // Enable conversion of body response.
  // Use one of "arrayBuffer", "blob", "formData", "json", "stream", "text", or
  // undefined to ignore response body.
  responseType: 'json',
  // Transform response error before returning.
  transformError: (error: FetchResponseError, response: FetchClientResponse) => {
    // Return custom server error.
    if (error.response.body?.message) {
      return new FetchResponseError(error.response.body.message, response)
    }
    return error
  },
  // Transform request options and headers before sending.
  // Several functions can be passed (all executed sequentially).
  transformRequest: [
    (url, options) => ({
      ...options,
      headers: {
        ...options.headers,
        // Add request date to each request
        'x-request-date': Date.now().toString(),
      },
    }),
  ],
  // Transform response Body before returning.
  // Several functions can be passed (all executed sequentially).
  transformResponse: [
    (body, response) => ({
      ...body,
      // Add response date to each response
      receivedAt: Date.now(),
    }),
  ],
})

Changelog

History of releases is in the changelog on GitHub.

License

The code is released under the MIT License.