@jalik/fetch-client
v2.3.2
Published
Fetch wrapper to manage requests/responses more easier
Readme
@jalik/fetch-client
HTTP client based on Fetch API with error handling and other DX improvements.
Features
- Based on Fetch API (RequestInit + Response), with extra options
- Works in the browser (use fetch polyfill for old browsers)
- Works in NodeJS (since version 18)
- 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 declarations ♥
Sandbox
Play with the lib here: https://codesandbox.io/s/jalik-fetch-client-demo-8rolt2?file=/src/index.js
Installing
npm i -P @jalik/fetch-clientyarn add @jalik/fetch-clientCreating 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: 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:
bodyis serialized to JSONContent-Type: application/jsonis 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:
bodyis serialized to JSONContent-Type: application/jsonis 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:
bodyis serialized to JSONContent-Type: application/jsonis 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.
