@jalik/fetch-client
TypeScript icon, indicating that this package has built-in type declarations

2.3.0 • Public • Published

@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.

Readme

Keywords

none

Package Sidebar

Install

npm i @jalik/fetch-client

Weekly Downloads

6

Version

2.3.0

License

MIT

Unpacked Size

60.1 kB

Total Files

11

Last publish

Collaborators

  • jalik