zlFetch is a wrapper around fetch that provides you with a convenient way to make requests.
It's features are as follows:
-
Quality of life improvements over the native
fetchfunction- Use the response right away without using
response.json(),response.text(), orresponse.blob(). - Promise-like error handling — all 400 and 500 errors are directed into the
catchblock automatically. - Easy error handling when using
await— errors can be returned so you don't have to write atry/catchblock. - Built-in abort functionality
- Streaming capabilitiies with pure Fetch and Event Source
- Use the response right away without using
-
Additional improvements over the native
fetchfunctionContent-Typeheaders are set automatically based on thebodycontent.- Get everything you need about your response —
headers,body,status, and more. - Debug your request without looking at the Network panel
- Shorthand for
GET,POST,PUT,PATCH, andDELETEmethods - Helper for generating query strings so you don't have to mess with query parameters.
- Generates authorization headers with an
authproperty. - Create instances to hold url and options so you don't have to repeat yourself.
Note: zlFetch is a ESM library since v4.0.0.
- zlFetch
- Table of Contents
# Installing through npm
npm install zl-fetch --saveThen you can use it by importing it in your JavaScript file.
import zlFetch from 'zl-fetch'You can import zlFetch directly into JavaScript through a CDN.
To do this, you first need to set your script's type to module, then import zlFetch.
<script type="module">
import zlFetch from 'https://cdn.jsdelivr.net/npm/[email protected]/src/index.js'
</script>You can use zlFetch just like a normal fetch function. The only difference is you don't have to write a response.json or response.text method anymore!
zlFetch handles it for you automatically so you can go straight to using your response.
zlFetch('url')
.then(response => console.log(response))
.catch(error => console.log(error))zlFetch contains shorthand methods for these common REST methods so you can use them quickly.
zlFetch.get(/* some-url */)
zlFetch.post(/* some-url */)
zlFetch.put(/* some-url */)
zlFetch.patch(/* some-url */)
zlFetch.delete(/* some-url */)zlFetch supports json, text, and blob response types so you don't have to write response.json(), response.text() or response.blob().
It also supports streams. See streaming for a better understanding of how this works.
Other response types are not supported right now. If you need to support other response types, consider using your own response handler
zlFetch sends you all the data you need in the response object. This includes the following:
headers: response headersbody: response bodystatus: response statusstatusText: response status textresponse: original response from Fetch
We do this so you don't have to fish out the headers, status, statusText or even the rest of the response object by yourself.
New in v4.0.0: You can debug the request object by adding a debug option. This will reveal a debug object that contains the request being constructed.
urlmethodheadersbody
zlFetch('url', { debug: true })
.then({ debug } => console.log(debug))zlFetch directs all 400 and 500 errors to the catch method. Errors contain the same information as a response.
headers: response headersbody: response bodystatus: response statusstatusText: response status textresponse: original response from fetch
This makes is zlFetch super easy to use with promises.
zlFetch('some-url').catch(error => {
/* Handle error */
})
// The above request can be written in Fetch like this:
fetch('some-url')
.then(response => {
if (!response.ok) {
Promise.reject(response.json)
}
})
.catch(error => {
/* Handle error */
})zlFetch lets you pass all errors into an errors object. You can do this by adding a returnError option. This is useful when you work a lot with async/await.
const { response, error } = await zlFetch('some-url', {
returnError: true,
})zlFetch supports streaming in v6.2.0. It detects streams when you pass in stream: true as an option. It also provides a readable stream helper to decode the stream.
The following can be detected as streams:
Content-Typeheader istext/event-stream- Header contains
Transfer-Encoding: chunked - There is no
Content-Lengthheader
The decoded stream is stored inside response.body so you can simply loop through it to get your chunks. Below are a few caveats you need to know because the implementation changes slightly due depending on how servers implement streaming.
zlFetch decodes the request body for you automatically for server-sent events. Just loop through the request.body to get your chunks.
const response = await zlFetch('/sse-endpoint', { stream: true })
for await (const chunk of response.body) {
// Do something with chunk
chunks.push(chunk)
}The pure zlFetch function might not be the best at handling SSE because it doesn't reconnect automatically when the connection is lost. See Streaming with Event Source for a recommended approach.
The Transfer-Encoding header does not reach the browser the stream will not be decoded automatically. To decode it in the browser, use the readStream helper we provided. No need for this extra step if you're sending the Fetch request from a server.
import { readStream } from 'zl-fetch'
const response = await zlFetch('/sse-endpoint', { stream: true })
// For Browsers
const stream = readStream(response.body)
for await (const chunk of stream) {
/* ...*/
}
// For Servers
for await (const chunk of response.body) {
/* ... */
}zlFetch detects it's a stream if there is no Content-Length header. For these streams, the Transfer-Encoding: chunked rules above apply.
You can abort a request — both normal and streams — using the same abort() method. This is useful for:
- Canceling long-running requests
- Stopping requests when a user navigates away
- Implementing request timeouts
- Canceling requests when new data is needed
// With promises
const request = zlFetch('endpoint')
// Aborts the request
request.abort()
// Handle the abort
request.catch(error => {
if (error.name === 'AbortError') {
console.log('Request was aborted')
}
})We've added the abort function to the .then call so you can call it while handling the response.
// With promises
const request = zlFetch('endpoint')
.then(response => {
// Aborts the request
response.abort()
})
.catch(error => {
// Handle the abort error
if (error.name === 'AbortError') {
console.log('Request was aborted')
}
})You can also handle aborts when using async/await:
try {
const response = await zlFetch('endpoint')
// Aborts the request
response.abort()
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was aborted')
}
}If you wish to, you can pass in your abort controller as well. No need to pass the abort signal — we'll create a signal from that controller for the abort method.
const customAbortController = new AbortController()
const response = await zlFetch('endpoint', {
controller: customAbortController,
})
// Abort the request
response.abort()You can add query or queries as an option and zlFetch will create a query string for you automatically. Use this with GET requests.
zlFetch('some-url', {
queries: {
param1: 'value1',
param2: 'to encode',
},
})
// The above request can be written in Fetch like this:
fetch('url?param1=value1¶m2=to%20encode')zlFetch sets Content-Type appropriately depending on your body data. It supports three kinds of data:
- Object
- Query Strings
- Form Data
If you pass in an object, zlFetch will set Content-Type to application/json. It will also JSON.stringify your body so you don't have to do it yourself.
zlFetch.post('some-url', {
body: { message: 'Good game' },
})
// The above request is equivalent to this
fetch('some-url', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'Good game' }),
})zlFetch contains a toObject helper that lets you convert Form Data into an object. This makes it super easy to zlFetch with forms.
import { toObject } from 'zl-fetch'
const data = new FormData(form.elements)
zlFetch('some-url', {
body: toObject(data),
})If you pass in a string, zlFetch will set Content-Type to application/x-www-form-urlencoded.
zlFetch also contains a toQueryString method that can help you convert objects to query strings so you can use this option easily.
import { toQueryString } from 'zl-fetch'
zlFetch.post('some-url', {
body: toQueryString({ message: 'Good game' }),
})
// The above request is equivalent to this
fetch('some-url', {
method: 'post',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'message=Good%20game',
})If you pass in a Form Data, zlFetch will let the native fetch function handle the Content-Type. Generally, this will use multipart/form-data with the default options. If you use this, make sure your server can receive multipart/form-data!
import { toObject } from 'zl-fetch'
const data = new FormData(form.elements)
zlFetch('some-url', { body: data })
// The above request is equivalent to this
fetch('some-url', { body: data })
// Your server should be able to receive multipart/form-data if you do this. If you're using Express, you can a middleware like multer to make this possible:
import multer from 'multer'
const upload = multer()
app.use(upload.array())Breaking Change in v5.0.0: If you pass in a Content-Type header, zlFetch will not set format your body content anymore. We expect you to be able to pass in the correct data type. (We had to do this to support the new API mentioned above).
If you provide zlFetch with an auth property, it will generate an Authorization Header for you.
If you pass in a string (commonly for tokens) , it will generate a Bearer Auth.
zlFetch('some-url', { auth: 'token12345' })
// The above request can be written in Fetch like this:
fetch('some-url', {
headers: { Authorization: `Bearer token12345` },
})If you pass in an object, zlFetch will generate a Basic Auth for you.
zlFetch('some-url', {
auth: {
username: 'username'
password: '12345678'
}
})
// The above request can be written in Fetch like this:
fetch('some-url', {
headers: { Authorization: `Basic ${btoa('username:12345678')}` }
});You can create an instance of zlFetch with predefined options. This is super helpful if you need to send requests with similar options or url.
urlis requiredoptionsis optional
import { createZLFetch } from 'zl-fetch'
// Creating the instance
const api = zlFetch(baseUrl, options)All instances have shorthand methods as well.
// Shorthand methods
const response = api.get(/* ... */)
const response = api.post(/* ... */)
const response = api.put(/* ... */)
const response = api.patch(/* ... */)
const response = api.delete(/* ... */)New in v5.0.0
You can now use a zlFetch instance without passing a URL. This is useful if you have created an instance with the right endpoints.
import { createZLFetch } from 'zl-fetch'
// Creating the instance
const api = zlFetch(baseUrl, options)All instances have shorthand methods as well.
// Shorthand methods
const response = api.get() // Without URL, without options
const response = api.get('some-url') // With URL, without options
const response = api.post('some-url', { body: 'message=good+game' }) // With URL, with options
const response = api.post({ body: 'message=good+game' }) // Without URL, with optionsIf you want to handle a response not supported by zlFetch, you can pass customResponseParser: true into the options. This returns the response from a normal Fetch request without any additional treatments from zlFetch. You can then use response.json() or other methods as you deem fit.
const response = await zlFetch('url', {
customResponseParser: true,
})
const data = await response.arrayBuffer()We created a small wrapper around EventSource for streaming with SSE.
On the browser, we use the Browser's Event Source — with a few addons — as the default. On the server, we wrap zlFetch with a retry functionality to make it similar to the Browser version.
import { zlEventSource } from 'zl-fetch'
const source = zlEventSource(url, options)zlEventSource lets you listen to any events by providing the event as a callback in options. Custom events are also supported. This provides a simpler API for usage.
import { zlEventSource } from 'zl-fetch'
const source = zlEventSource(url, {
open: data => console.log(data),
message: data => console.log(data),
ping: data => console.log(data), // This is a custom event
close: data => console.log(data),
})The browser's event source capabilities are quite limited — you can only send a GET request. If you want to be able to send POST requests, add Authorization, the best method is to use the wrapped zlFetch version.
To use this, just set useFetch to true.
On servers, we automatically use the wrapped zlFetch version.
import { zlEventSource } from 'zl-fetch'
const source = zlEventSource(url, { useFetch: true }, fetchOptions)You can continue monitoring for events with the event callbacks.
import { zlEventSource } from 'zl-fetch'
const source = zlEventSource(
url,
{
useFetch: true,
message: data => console.log(data),
},
fetchOptions,
)Retry intervals will be set according to the retry property sent in the SSE response. If it's not present, you can adjust the retry interval with the retry property.
import { zlEventSource } from 'zl-fetch'
const source = zlEventSource(
url,
{
useFetch: true,
retry: 3000, // In milliseconds
},
fetchOptions,
)zlEventSource will close automatically if the server sents a close event. If you wish to terminate the session earlier, you can call the close method on the event source.
const source = zlEventSource(url)
source.close() // Terminates the event source connection