Experience an upgraded fetch API ๐. Operates seamlessly on node, browsers, and workers ๐ป๐๐ ๏ธ.
Install:
# nyxi
nyxi fetchwizard
#pnpm
pnpm add fetchwizard
# npm
npm i fetchwizard
# yarn
yarn add fetchwizard
Import:
// ESM / Typescript
import { fetchwizard } from 'fetchwizard'
// CommonJS
const { fetchwizard } = require('fetchwizard')
We use conditional exports to detect Node.js
and automatically use nyxblabs/fetch-for-all. If globalThis.fetch
is available, it will be used instead. To leverage Node.js 17.5.0 experimental native fetch API, use the ๐ฌ --experimental-fetch
flag.
By setting the FETCH_KEEP_ALIVE
environment variable to true
, an โก๏ธ http/https agent will be registered that keeps sockets around even when there are no outstanding requests, so they can be used for future requests without having to reestablish a TCP connection.
Note: This option can potentially introduce memory leaks. Please check node-fetch/node-fetch#1325.
fetchwizard
will smartly parse JSON and native values using nyxjson, falling back to text if it fails to parse.
const { users } = await fetchwizard('/api/users')
For binary content types, fetchwizard
will instead return a ๐ฆ Blob
object.
You can optionally provide a different parser than destr, or specify ๐ฆ blob
, ๐งฑ arrayBuffer
or ๐ text
to force parsing the body with the respective FetchResponse
method.
// Use JSON.parse
await fetchwizard('/movie?lang=en', { parseResponse: JSON.parse })
// Return text as is
await fetchwizard('/movie?lang=en', { parseResponse: txt => txt })
// Get the blob version of the response
await fetchwizard('/api/generate-image', { responseType: 'blob' })
fetchwizard
automatically stringifies the request body (if an object is passed) and adds JSON ๐ Content-Type
and ๐ฅ Accept
headers (for put
, patch
, and post
requests).
const { users } = await fetchwizard('/api/users', { method: 'POST', body: { some: 'json' } })
fetchwizard
automatically throws errors when response.ok
is false
with a friendly error message and compact stack (hiding internals).
Parsed error body is available with error.data
. You may also use the FetchError
type.
await fetchwizard('http://google.com/404')
// FetchError: 404 Not Found (http://google.com/404)
// at async main (/project/playground.ts:4:3)
In order to bypass errors as response you can use error.data
:
await fetchwizard(...).catch((error) => error.data)
fetchwizard
automatically retries the request if an error occurs. The default number of retries is 1
(except for POST
, PUT
, PATCH
, and DELETE
methods, which have a default of 0
retries).
await fetchwizard('http://google.com/404', {
retry: 3
})
Responses can be type-assisted:
const article = await fetchwizard<Article>(`/api/article/${id}`)
// Auto complete working with article.id
By using the baseURL
option, fetchwizard
prepends it while respecting trailing/leading slashes and query search parameters for baseURL using url-ops:
await fetchwizard('/config', { baseURL })
By using the query
option (or params
as an alias), fetchwizard
adds query search parameters to the URL by preserving the query in the request itself using url-ops:
await fetchwizard('/movie?lang=en', { query: { id: 123 } })
It is possible to provide async interceptors to hook into the lifecycle events of fetchwizard
calls.
You might want to use fetchwizard.create
to set shared interceptors.
The onRequest
interceptor is called as soon as fetchwizard
is being called, allowing you to modify options or perform simple logging.
await fetchwizard('/api', {
async onRequest({ request, options }) {
// Log request
console.log('[fetch request]', request, options)
// Add `?t=1640125211170` to query search params
options.query = options.query || {}
options.query.t = new Date()
}
})
The onRequestError
interceptor will be called when the fetch request fails.
await fetchwizard('/api', {
async onRequestError({ request, options, error }) {
// Log error
console.log('[fetch request error]', request, error)
}
})
The onResponse
interceptor will be called after the fetch
call and parsing the response body.
await fetchwizard('/api', {
async onResponse({ request, response, options }) {
// Log response
console.log('[fetch response]', request, response.status, response.body)
}
})
The onResponseError
interceptor is similar to onResponse
, but it will be called when the fetch request is successful (response.ok
is not true
).
await fetchwizard('/api', {
async onResponseError({ request, response, options }) {
// Log error
console.log('[fetch response error]', request, response.status, response.body)
}
})
This utility is useful if you need to use common options across several fetch calls.
Note: Defaults will be cloned at one level and inherited. Be careful with nested options like headers
.
const apiFetch = fetchwizard.create({ baseURL: '/api' })
apiFetch('/test') // Same as fetchwizard('/test', { baseURL: '/api' })
By using the headers
option, fetchwizard
adds extra headers in addition to the default headers of the request:
await fetchwizard('/movies', {
headers: {
'Accept': 'application/json',
'Cache-Control': 'no-cache'
}
})
If you need to use an HTTP(S) Agent, you can add the agent
option with https-proxy-agent
(for Node.js only):
import { HttpsProxyAgent } from 'https-proxy-agent'
await fetchwizard('/api', {
agent: new HttpsProxyAgent('http://example.com')
})
If you need to access the raw response (for headers, etc.), you can use fetchwizard.raw
:
const response = await fetchwizard.raw('/sushi')
// response._data
// response.headers
// ...
As a shortcut, you can use fetchwizard.native
which provides the native fetch
API.
const json = await fetchwizard.native('/sushi').then(r => r.json())
- โจ All targets are exported with Module and CommonJS format and named exports
- โ๏ธ No export is transpiled for the sake of modern syntax
- ๐ You probably need to transpile
fetchwizard
,destr
, andufo
packages with babel for ES5 support
- ๐ You probably need to transpile
- ๐ฅ You need to polyfill
fetch
global for supporting legacy browsers like using unfetch
โ Why export is called fetchwizard
instead of fetch
?
Using the same name of fetch
can be confusing since API is different but still it is a fetch so using closest possible alternative. You can however, import { fetch }
from fetchwizard
which is auto polyfilled for Node.js and using native otherwise. ๐
โ Why not having default export?
Default exports are always risky to be mixed with CommonJS exports.
This also guarantees we can introduce more utils without breaking the package and also encourage using fetchwizard
name. ๐ฆ
โ Why not transpiled?
By keep transpiling libraries we push web backward with legacy code which is unneeded for most of the users. โฎ๏ธ
If you need to support legacy users, you can optionally transpile the library in your build pipeline. โ๏ธ
MIT - Made with ๐