https://github.com/L-Blondy/up-fetch
https://github.com/L-Blondy/up-fetch
Last synced: about 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/L-Blondy/up-fetch
- Owner: L-Blondy
- Created: 2023-03-15T09:55:00.000Z (about 2 years ago)
- Default Branch: master
- Last Pushed: 2024-04-13T17:30:24.000Z (about 1 year ago)
- Last Synced: 2024-04-14T07:09:50.977Z (about 1 year ago)
- Language: TypeScript
- Size: 328 KB
- Stars: 5
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
- my-awesome-list - up-fetch - Blondy | 1076 | (TypeScript)
README
upfetch - advanced fetch client builder
![]()
_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.
[中文文档 (AI 翻译)](./README_ZH.md)
## 🚀 Try v2 Beta!
Version 2 of upfetch is now available in beta! The changes mainly impact advanced use cases, so most projects won’t require any modifications. Give it a try with:
```bash
npm i up-fetch@2.0.0-beta.3
```Check out the [Migration Guide](https://github.com/L-Blondy/up-fetch/blob/v2.0/MIGRATION_v1_v2.md) for details about changes and how to upgrade. \
For a complete overview of new features, see the [v2 documentation](https://github.com/L-Blondy/up-fetch/tree/v2.0/README.md).## Table of Contents
- [Highlights](#️-highlights)
- [QuickStart](#️-quickstart)
- [Key Features](#️-key-features)
- [Request Configuration](#️-request-configuration)
- [Simple Query Parameters](#️-simple-query-parameters)
- [Automatic Body Handling](#️-automatic-body-handling)
- [Schema Validation](#️-schema-validation)
- [Lifecycle Hooks](#️-lifecycle-hooks)
- [Timeout](#️-timeout)
- [Error Handling](#️-error-handling)
- [Usage](#️-usage)
- [Authentication](#️-authentication)
- [Delete a default option](#️-delete-a-default-option)
- [FormData](#️-formdata)
- [HTTP Agent](#️-http-agent)
- [Multiple fetch clients](#️-multiple-fetch-clients)
- [Advanced Usage](#️-advanced-usage)
- [Error as value](#️-error-as-value)
- [Custom response parsing](#️-custom-response-parsing)
- [Custom response errors](#️-custom-response-errors)
- [Custom params serialization](#️-custom-params-serialization)
- [Custom body serialization](#️-custom-body-serialization)
- [Defaults based on the request](#️-defaults-based-on-the-request)
- [API Reference](#️-api-reference)
- [Feature Comparison](#️-feature-comparison)
- [Environment Support](#️-environment-support)## ➡️ Highlights
- 🚀 **Lightweight** - 1.2kB gzipped, no dependency
- 🔒 **Typesafe** - Validate API responses with [zod][zod], [valibot][valibot] or [arktype][arktype]
- 🛠️ **Practical API** - Use objects for `params` and `body`, get parsed responses automatically
- 🎨 **Flexible Config** - Set defaults like `baseUrl` or `headers` once, use everywhere
- 🤝 **Familiar** - same API as fetch with additional options and sensible defaults## ➡️ QuickStart
```bash
npm i up-fetch
```Create a new upfetch instance:
```ts
import { up } from 'up-fetch'export const upfetch = up(fetch)
```Make a fetch request with schema validation:
```ts
import { upfetch } from './upfetch'
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:
```ts
const upfetch = up(fetch, () => ({
baseUrl: 'https://a.b.c',
timeout: 30000,
}))
```Check out the the [API Reference][api-reference] for the full list of options.
### ✔️ Simple Query Parameters
👎 With raw fetch:
```ts
fetch(
`https://api.example.com/todos?search=${search}&skip=${skip}&take=${take}`,
)
```👍 With _upfetch_:
```ts
upfetch('/todos', {
params: { search, skip, take },
})
```Use the [serializeParams][api-reference] option to customize the query parameter serialization.
### ✔️ Automatic Body Handling
👎 With raw fetch:
```ts
fetch('https://api.example.com/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'New Todo' }),
})
```👍 With _upfetch_:
```ts
upfetch('/todos', {
method: 'POST',
body: { title: 'New Todo' },
})
```_upfetch_ also supports all [fetch body types](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#body).
Check out the [serializeBody][api-reference] option to customize the body serialization.
### ✔️ Schema Validation
Since _upfetch_ follows the [Standard Schema Specification][standard-schema] it can be used with any schema library that implements the spec. \
See the full list [here][standard-schema-libs].👉 With **zod** 3.24+
```ts
import { z } from 'zod'const posts = await upfetch('/posts/1', {
schema: z.object({
id: z.number(),
title: z.string(),
}),
})
```👉 With **valibot** 1.0+
```ts
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:
```ts
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 timeout for one request:
```ts
upfetch('/todos', {
timeout: 3000,
})
```Set a default timeout for all requests:
```ts
const upfetch = up(fetch, () => ({
timeout: 5000,
}))
```### ✔️ Error Handling
#### 👉 ResponseError
Raised when `response.ok` is `false`. \
Use `isResponseError` to identify this error type.```ts
import { isResponseError } from 'up-fetch'try {
await upfetch('/todos/1')
} catch (error) {
if (isResponseError(error)) {
console.log(error.status)
}
}
```- Use the [parseRejected][api-reference] option to throw a custom error instead.
- Use the [reject][api-reference] option to decide **when** to throw.#### 👉 ValidationError
Raised when schema validation fails. \
Use `isValidationError` to identify this error type.```ts
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:
```ts
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`:
```ts
upfetch('/todos', {
signal: undefined,
})
```### ✔️ FormData
Grab the FormData from a `form`.
```ts
const form = document.querySelector('#my-form')upfetch('/todos', {
method: 'POST',
body: new FormData(form),
})
```Or create FormData from an object:
```ts
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](https://github.com/nodejs/undici) instead of the native fetch implementation.
On a single request:
```ts
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:
```ts
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:
```ts
const fetchMovie = up(fetch, () => ({
baseUrl: "https://api.themoviedb.org",
headers: {
accept: "application/json",
Authorization: `Bearer ${process.env.API_KEY}`,
},
}))const fetchFile = up(fetch, () => ({
parseResponse: async (res) => {
const name = res.url.split('/').at(-1) ?? ''
const type = res.headers.get('content-type') ?? ''
return new File([await res.blob()], name, { type })
},
}))
```## ➡️ 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 `reject` to return `false`. \
This allows you to customize the `parseResponse` function to return both successful data and error responses in a structured format.```ts
const upfetch = up(fetch, () => ({
reject: () => false,
parseResponse: async (response) => {
const json = await response.json()
return response.ok
? { data: json, error: null }
: { data: null, error: json }
},
}))
```Usage:
```ts
const { data, error } = await upfetch('/users/1')
```### ✔️ Custom response parsing
By default _upfetch_ is able to parse `json` and `text` sucessful responses automatically.
The `parseResponse` method is called when `reject` returns `false`.
You can use that option to parse other response types.```ts
const upfetch = up(fetch, () => ({
parseResponse: (response) => response.blob(),
}))
```💡 Note that the `parseResponse` method is called only when `reject` returns `false`.
### ✔️ Custom response errors
By default _upfetch_ throws a `ResponseError` when `reject` returns `true`.
If you want to throw a custom error instead, you can pass a function to the `parseRejected` option.
```ts
const upfetch = up(fetch, () => ({
parseRejected: async (response) => {
const status = response.status
const data = await response.json()
return new CustomError(status, data)
},
}))
```### ✔️ 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.
```ts
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 valid body type** by typing its first argument
- **transform the body** in a valid `BodyInit` typeThe following example show how to restrict the valid body type to `Record` and serialize it using `JSON.stringify`:
```ts
// Restrict the body type to Record and serialize it
const upfetch = up(fetch, () => ({
serializeBody: (body: Record) => JSON.stringify(body),
}))// ❌ type error: the body is not a Record
upfetch('https://a.b.c/todos', {
method: 'POST',
body: [['title', 'New Todo']],
})// ✅ works fine with Record
upfetch('https://a.b.c/todos', {
method: 'POST',
body: { title: 'New Todo' },
})
```The following example uses `superjson` to serialize the body. The valid body type is inferred from `SuperJSON.stringify`.
```ts
import SuperJSON from 'superjson'const upfetch = up(fetch, () => ({
serializeBody: SuperJSON.stringify,
}))
```### ✔️ Defaults based on the request
The default options receive the fetcher arguments, this allows you to tailor the defaults based on the actual request.
```ts
const upfetch = up(fetch, (input, options) => ({
baseUrl: 'https://example.com/',
headers: {
// Add authentication only for protected routes
Authorization:
typeof input === 'string' && input.startsWith('/api/protected/')
? `Bearer ${getToken()}`
: undefined,
},
// Add tracking params only for public endpoints
params: {
trackingId:
typeof input === 'string' && input.startsWith('/public/')
? crypto.randomUUID()
: undefined,
},
// Increase timeout for long-running operations
timeout:
typeof input === 'string' && input.startsWith('/export/') ? 30000 : 5000,
}))
```## ➡️ API Reference
### up(fetch, getDefaultOptions?)
Creates a new upfetch instance with optional default options.
```ts
function up(
fetchFn: typeof globalThis.fetch,
getDefaultOptions?: (fetcherOptions: FetcherOptions) => 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. |
| `parseRejected` | `(response, options) => error` | The default error response parser.
If omitted `json` and `text` response are parsed automatically |
| `serializeBody` | `(body) => BodyInit` | The default body serializer.
Restrict the valid `body` type by typing its first argument. |
| `serializeParams` | `(params) => string` | The default query parameter serializer. |
| `timeout` | `number` | The default timeout in milliseconds. |
| `reject` | `(response) => boolean` | Decide when to reject the response. |
| _...and all other fetch options_ | | |### upfetch(url, options?)
Makes a fetch request with the given options.
```ts
function upfetch(
url: string | URL | Request,
options?: FetcherOptions,
): Promise
```Options:
| Option | Signature | Description |
| -------------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl` | `string` | Base URL for the request. |
| `params` | `object` | The query parameters. |
| `parseResponse` | `(response, options) => data` | The success response parser. |
| `parseRejected` | `(response, options) => error` | The error response parser. |
| `schema` | `StandardSchemaV1` | The schema to validate the response against.
The schema must follow the [Standard Schema Specification][standard-schema]. |
| `serializeBody` | `(body) => BodyInit` | The body serializer.
Restrict the valid `body` type by typing its first argument. |
| `serializeParams` | `(params) => string` | The query parameter serializer. |
| `timeout` | `number` | The timeout in milliseconds. |
| `reject` | `(response) => boolean` | Decide when to reject the response. |
| _...and all other fetch options_ | | |
### isResponseError(error)
Checks if the error is a `ResponseError`.
### isValidationError(error)
Checks if the error is a `ValidationError`.
### isJsonifiable(value)
Determines whether a value can be safely converted to `json`.
Are considered jsonifiable:
- plain objects
- arrays
- class instances with a `toJSON` method## ➡️ Feature Comparison
Check out the [Feature Comparison][comparison] table to see how _upfetch_ compares to other fetching libraries.
## ➡️ Environment Support
- ✅ Browsers (Chrome, Firefox, Safari, Edge)
- ✅ Node.js (18.0+)
- ✅ Bun
- ✅ Deno
- ✅ Cloudflare Workers
- ✅ Vercel Edge Runtime
Share on:
[![s][bsky-badge]][bsky-link]
[![Share on Twitter][tweet-badge]][tweet-link]
[bsky-badge]: https://img.shields.io/badge/Bluesky-0085ff?logo=bluesky&logoColor=fff
[bsky-link]: https://bsky.app/intent/compose?text=https%3A%2F%2Fgithub.com%2FL-Blondy%2Fup-fetch
[tweet-badge]: https://img.shields.io/badge/Twitter-0f1419?logo=x&logoColor=fff
[tweet-link]: https://twitter.com/intent/tweet?text=https%3A%2F%2Fgithub.com%2FL-Blondy%2Fup-fetch[zod]: https://zod.dev/
[valibot]: https://valibot.dev/
[arktype]: https://arktype.dev/
[standard-schema]: https://github.com/standard-schema/standard-schema
[standard-schema-libs]: https://github.com/standard-schema/standard-schema?tab=readme-ov-file#what-schema-libraries-implement-the-spec
[api-reference]: #️-api-reference
[comparison]: https://github.com/L-Blondy/up-fetch/blob/master/COMPARISON.md