Fetch API configuration tool with built-in validation and sensible defaults.
- 🚀 Lightweight - 1.2kB gzipped, no dependency
- 🛠️ Practical API - Use objects for
params
andbody
, get parsed responses automatically - 🎨 Flexible Config - Set defaults like
baseUrl
orheaders
once, use everywhere - 🔒 Type Safe - Validate API responses with zod, valibot or arktype
- 🤝 Familiar - same API as fetch with additional options and sensible defaults
npm i up-fetch
Create a new upfetch instance:
import { up } from 'up-fetch'
const upfetch = up(fetch)
Make a fetch request with schema validation:
import { z } from 'zod'
import { todoSchema } from './schema'
const todos = await upfetch('https://a.b.c/todos', {
params: { take: 12, skip: 0 },
schema: z.array(todoSchema),
})
The response is already parsed and properly typed based on the schema.
Set defaults for all requests when creating an instance:
const upfetch = up(fetch, () => ({
baseUrl: 'https://a.b.c',
}))
Check out the the API Reference for the full list of options.
👎 With raw fetch:
fetch(
`https://api.example.com/todos?search=${search}&skip=${skip}&take=${take}`,
)
👍 With up-fetch:
upfetch('/todos', {
params: { search, skip, take },
})
Use the serializeParams option to customize the query parameter serialization.
👎 With raw fetch:
fetch('https://api.example.com/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'New Todo' }),
})
👍 With up-fetch:
upfetch('/todos', {
method: 'POST',
body: { title: 'New Todo' },
})
up-fetch also supports all fetch body types.
Check out the serializeBody option to customize the body serialization.
Since up-fetch follows the Standard Schema Specification it can be used with any schema library that implements the spec.
See the full list here.
👉 With zod 3.24+
import { z } from 'zod'
const posts = await upfetch('/posts/1', {
schema: z.object({
id: z.number(),
title: z.string(),
}),
})
👉 With valibot 1.0+
import { object, string, number } from 'valibot'
const posts = await upfetch('/posts/1', {
schema: object({
id: number(),
title: string(),
}),
})
Control request/response lifecycle with simple hooks:
const upfetch = up(fetch, () => ({
onRequest: (options) => {
// Called before the request is made, options might be mutated here
},
onSuccess: (data, options) => {
// Called when the request successfully completes
},
onError: (error, options) => {
// Called when the request fails
},
}))
Set a default timeout for all requests:
const upfetch = up(fetch, () => ({
timeout: 5000,
}))
Use a different timeout for a specific request:
upfetch('/todos', {
timeout: 3000,
})
By default, up-fetch throws a ResponseError
when response.ok
is false
.
The error extends the Error class with the followimg properties:
status
: The HTTP status codedata
: The parsed error bodyoptions
: The options used for the requestresponse
: The raw Response
import { isResponseError } from 'up-fetch'
try {
await upfetch('/todos/1')
} catch (error) {
if (isResponseError(error)) {
console.log(error.data)
console.log(error.status)
}
}
Use the throwResponseError option to decide when to throw, or the parseResponseError option to customize what to throw.
You can easily add authentication to all requests by setting a default header:
const upfetch = up(fetch, () => ({
headers: { Authorization: localStorage.getItem('bearer-token') },
}))
The bearer token will be retrieved from localStorage
before each request.
Simply pass undefined
:
upfetch('/todos', (defaultOptions) => ({
signal: undefined,
}))
You can override default options for a specific request by passing a function as the 2nd argument:
upfetch('/todos', (defaultOptions) => ({
signal: condition ? AbortSignal.timeout(5000) : defaultOptions.signal,
}))
Grab the FormData from a form
.
const form = document.querySelector('#my-form')
upfetch('/todos', {
method: 'POST',
body: new FormData(form),
})
Or create FormData from an object:
import { serialize } from 'object-to-formdata'
const upfetch = up(fetch, () => ({
serializeBody: (body) => serialize(body),
}))
upfetch('https://a.b.c', {
method: 'POST',
body: { file: new File(['foo'], 'foo.txt') },
})
Since up-fetch is "fetch agnostic", you can use undici instead of the native fetch implementation.
On a single request:
import { fetch, Agent } from 'undici'
const upfetch = up(fetch)
const data = await upfetch('https://a.b.c', {
dispatcher: new Agent({
keepAliveTimeout: 10,
keepAliveMaxTimeout: 10,
}),
})
On all requests:
import { fetch, Agent } from 'undici'
const upfetch = up(fetch, () => ({
dispatcher: new Agent({
keepAliveTimeout: 10,
keepAliveMaxTimeout: 10,
}),
}))
You can create multiple upfetch instances with different defaults:
const fetchJson = up(fetch)
const fetchBlob = up(fetch, () => ({
parseResponse: (res) => res.blob(),
}))
const fetchText = up(fetch, () => ({
parseResponse: (res) => res.text(),
}))
Creates a new upfetch instance with optional default options.
function up(
fetchFn: typeof globalThis.fetch,
getDefaultOptions?: () => DefaultOptions,
): UpFetch
Option | Signature | Description |
---|---|---|
baseUrl |
string |
Base URL for all requests. |
params |
object |
The default query parameters. |
onRequest |
(options) => void |
Executes before the request is made. |
onError |
(error, options) => void |
Executes on error. |
onSuccess |
(data, options) => void |
Executes when the request successfully completes. |
parseResponse |
(response, options) => data |
The default success response parser. If omitted json and text response are parsed automatically. |
parseResponseError |
(response, options) => error |
The default error response parser. If omitted json and text response are parsed automatically |
serializeBody |
(body) => BodyInit |
The default body serializer. |
serializeParams |
(params) => string |
The default query parameter serializer. |
timeout |
number |
The default timeout in milliseconds. |
throwResponseError |
(response) => boolean |
Decide when to reject the response. |
...and all other fetch options |
Makes a fetch request with the given options.
function upfetch(
url: string | URL | Request,
options?: FetcherOptions | ((defaultOptions: UpOptions) => FetcherOptions),
): Promise<any>
Options:
Option | Signature | Description |
---|---|---|
baseUrl |
string |
Base URL for the request. |
params |
object |
The query parameters. |
parseResponse |
(response, options) => data |
The success response parser. |
parseResponseError |
(response, options) => error |
The error response parser. |
schema |
StandardSchemaV1 |
The schema to validate the response against. The schema must follow the Standard Schema Specification. |
serializeBody |
(body) => BodyInit |
The body serializer. |
serializeParams |
(params) => string |
The query parameter serializer. |
timeout |
number |
The timeout in milliseconds. |
throwResponseError |
(response) => boolean |
Decide when to reject the response. |
...and all other fetch options |
Check out the Feature Comparison table to see how up-fetch compares to other fetch libraries.
- ✅ Browsers (Chrome, Firefox, Safari, Edge)
- ✅ Node.js (20.3.0+)
- ✅ Bun
- ✅ Deno
- ✅ Cloudflare Workers
- ✅ Vercel Edge Runtime