Package Exports
- up-fetch
- up-fetch/package.json
Readme
upfetch - advanced fetch client builder for typescript
upfetch is an advanced fetch client builder with standard schema validation, automatic response parsing, smart defaults and more. Designed to make data fetching type-safe and developer-friendly while keeping the familiar fetch API.
➡️ Highlights
- 🚀 Lightweight - 1.2kB gzipped, no dependency
- 🔒 Typesafe - Validate API responses with zod, valibot or arktype
- 🛠️ Practical API - Use objects for
params
andbody
, get parsed responses automatically - 🎨 Flexible Config - Set defaults like
baseUrl
orheaders
once, use everywhere - 🤝 Familiar - same API as fetch with additional options and sensible defaults
➡️ QuickStart
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'
const user = await upfetch('https://a.b.c/users/1', {
schema: z.object({
id: z.number(),
name: z.string(),
avatar: z.string().url(),
}),
})
The response is already parsed and properly typed based on the schema.
upfetch extends the native fetch API, which means all standard fetch options are available.
➡️ Key Features
✔️ Request Configuration
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.
✔️ Simple Query Parameters
👎 With raw fetch:
fetch(
`https://api.example.com/todos?search=${search}&skip=${skip}&take=${take}`,
)
👍 With upfetch:
upfetch('/todos', {
params: { search, skip, take },
})
Use the serializeParams option to customize the query parameter serialization.
✔️ Automatic Body Handling
👎 With raw fetch:
fetch('https://api.example.com/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'New Todo' }),
})
👍 With upfetch:
upfetch('/todos', {
method: 'POST',
body: { title: 'New Todo' },
})
upfetch also supports all fetch body types.
Check out the serializeBody option to customize the body serialization.
✔️ Schema Validation
Since upfetch 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(),
}),
})
✔️ Lifecycle Hooks
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
},
}))
✔️ Timeout
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,
})
✔️ Error Handling
👉 ResponseError
Raised when response.ok
is false
.
Use isResponseError
to identify this error type.
import { isResponseError } from 'up-fetch'
try {
await upfetch('/todos/1')
} catch (error) {
if (isResponseError(error)) {
console.log(error.status)
}
}
- Use the parseResponseError option to throw a custom error instead.
- Use the throwResponseError option to decide when to throw.
👉 ValidationError
Raised when schema validation fails.
Use isValidationError
to identify this error type.
import { isValidationError } from 'up-fetch'
try {
await upfetch('/todos/1', { schema: todoSchema })
} catch (error) {
if (isValidationError(error)) {
console.log(error.issues)
}
}
➡️ Usage
✔️ Authentication
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.
✔️ Delete a default option
Simply pass undefined
:
upfetch('/todos', (defaultOptions) => ({
signal: undefined,
}))
✔️ Override a default option conditionally
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,
}))
✔️ FormData
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') },
})
✔️ HTTP Agent
Since upfetch 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,
}),
}))
✔️ Multiple fetch clients
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(),
}))
➡️ Advanced Usage
Error as value
While the Fetch API does not throw an error when the response is not ok, upfetch throws a ResponseError
instead.
If you'd rather handle errors as values, set throwResponseError
to return false
.
This allows you to customize the parseResponse
function to return both successful data and error responses in a structured format.
const upfetch = up(fetch, () => ({
throwResponseError: () => false,
parseResponse: async (response) => {
const json = await response.json()
return response.ok
? { data: json, error: null }
: { data: null, error: json }
},
}))
Usage:
const { data, error } = await upfetch('/users/1')
Custom params serialization
By default upfetch serializes the params using URLSearchParams
.
You can customize the params serialization by passing a function to the serializeParams
option.
import queryString from 'query-string'
const upfetch = up(fetch, () => ({
serializeParams: (params) => queryString.stringify(params),
}))
Custom body serialization
By default upfetch serializes the plain objects using JSON.stringify
.
You can customize the body serialization by passing a function to the serializeBody
option. It lets you:
- restrict the type of the body by typing its first argument
- transform the body in a valid
BodyInit
type
import superjson from 'superjson'
// Restrict the body type to Record<string, any>
type ValidBody = Record<string, any>
const upfetch = up(fetch, () => ({
serializeBody: (body: ValidBody) => superjson.stringify(body),
}))
With this setup, upfetch will only accept bodies of type ValidBody
and will serialize them using superjson
.
upfetch('https://a.b.c/todos', {
method: 'POST',
body: { title: 'New Todo' },
})
Custom response parsing
By default upfetch is able to parse json
and text
sucessful responses automatically.
The parseResponse
method is called when throwResponseError
returns false
.
You can use that option to parse other response types.
const upfetch = up(fetch, () => ({
parseResponse: (response) => response.blob(),
}))
Note that the parseResponse
method is called only when throwResponseError
returns false
.
Custom response errors
By default upfetch throws a ResponseError
when throwResponseError
returns true
.
If you want to throw a custom error instead, you can pass a function to the parseResponseError
option.
const upfetch = up(fetch, () => ({
parseResponseError: async (response) => {
const status = response.status
const data = await response.json()
return new CustomError(status, data)
},
}))
➡️ API Reference
up(fetch, getDefaultOptions?)
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 |
upfetch(url, 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 |
Feature Comparison
Check out the Feature Comparison table to see how upfetch compares to other fetch libraries.
➡️ Environment Support
- ✅ Browsers (Chrome, Firefox, Safari, Edge)
- ✅ Node.js (20.3.0+)
- ✅ Bun
- ✅ Deno
- ✅ Cloudflare Workers
- ✅ Vercel Edge Runtime