# ofetch

<!-- automd:badges -->

[![npm version](https://img.shields.io/npm/v/ofetch)](https://npmjs.com/package/ofetch)
[![npm downloads](https://img.shields.io/npm/dm/ofetch)](https://npm.chart.dev/ofetch)

<!-- /automd -->

A better fetch API. Works on node, browser, and workers.

> [!IMPORTANT]
> You are on v2 (alpha) development branch. See [v1](https://github.com/unjs/ofetch/tree/v1) for v1 docs.

<details>
  <summary>Spoiler</summary>
  <img src="https://media.giphy.com/media/Dn1QRA9hqMcoMz9zVZ/giphy.gif">
</details>

## 🚀 Quick Start

Install:

```bash
npx nypm i ofetch
```

Import:

```js
import { ofetch } from "ofetch";
```

## ✔️ Parsing Response

`ofetch` smartly parse JSON responses.

```js
const { users } = await ofetch("/api/users");
```

For binary content types, `ofetch` will instead return a `Blob` object.

You can optionally provide a different parser than `JSON.parse`, or specify `blob`, `arrayBuffer`, `text` or `stream` to force parsing the body with the respective `FetchResponse` method.

```js
// Return text as is
await ofetch("/movie?lang=en", { parseResponse: (txt) => txt });

// Get the blob version of the response
await ofetch("/api/generate-image", { responseType: "blob" });

// Get the stream version of the response
await ofetch("/api/generate-image", { responseType: "stream" });
```

## ✔️ JSON Body

If an object or a class with a `.toJSON()` method is passed to the `body` option, `ofetch` automatically stringifies it.

`ofetch` utilizes `JSON.stringify()` to convert the passed object. Classes without a `.toJSON()` method have to be converted into a string value in advance before being passed to the `body` option.

For `PUT`, `PATCH`, and `POST` request methods, when a string or object body is set, `ofetch` adds the default `"content-type": "application/json"` and `accept: "application/json"` headers (which you can always override).

Additionally, `ofetch` supports binary responses with `Buffer`, `ReadableStream`, `Stream`, and [compatible body types](https://developer.mozilla.org/en-US/docs/Web/API/fetch#body). `ofetch` will automatically set the `duplex: "half"` option for streaming support!

**Example:**

```js
const { users } = await ofetch("/api/users", {
  method: "POST",
  body: { some: "json" },
});
```

## ✔️ Handling Errors

`ofetch` Automatically throws errors when `response.ok` is `false` with a friendly error message and compact stack (hiding internals).

A parsed error body is available with `error.data`. You may also use `FetchError` type.

```ts
await ofetch("https://google.com/404");
// FetchError: [GET] "https://google/404": 404 Not Found
//     at async main (/project/playground.ts:4:3)
```

To catch error response:

```ts
await ofetch("/url").catch((error) => error.data);
```

To bypass status error catching you can set `ignoreResponseError` option:

```ts
await ofetch("/url", { ignoreResponseError: true });
```

## ✔️ Auto Retry

`ofetch` Automatically retries the request if an error happens and if the response status code is included in `retryStatusCodes` list:

**Retry status codes:**

- `408` - Request Timeout
- `409` - Conflict
- `425` - Too Early ([Experimental](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Early-Data))
- `429` - Too Many Requests
- `500` - Internal Server Error
- `502` - Bad Gateway
- `503` - Service Unavailable
- `504` - Gateway Timeout

You can specify the amount of retry and delay between them using `retry` and `retryDelay` options and also pass a custom array of codes using `retryStatusCodes` option.

The default for `retry` is `1` retry, except for `POST`, `PUT`, `PATCH`, and `DELETE` methods where `ofetch` does not retry by default to avoid introducing side effects. If you set a custom value for `retry` it will **always retry** for all requests.

The default for `retryDelay` is `0` ms.

```ts
await ofetch("http://google.com/404", {
  retry: 3,
  retryDelay: 500, // ms
  retryStatusCodes: [404, 500], // response status codes to retry
});
```

## ✔️ Timeout

You can specify `timeout` in milliseconds to automatically abort a request after a timeout (default is disabled).

```ts
await ofetch("http://google.com/404", {
  timeout: 3000, // Timeout after 3 seconds
});
```

## ✔️ Type Friendly

The response can be type assisted:

```ts
const article = await ofetch<Article>(`/api/article/${id}`);
// Auto complete working with article.id
```

## ✔️ Adding `baseURL`

By using `baseURL` option, `ofetch` prepends it for trailing/leading slashes and query search params for baseURL using [ufo](https://github.com/unjs/ufo):

```js
await ofetch("/config", { baseURL });
```

## ✔️ Adding Query Search Params

By using `query` option (or `params` as alias), `ofetch` adds query search params to the URL by preserving the query in the request itself using [ufo](https://github.com/unjs/ufo):

```js
await ofetch("/movie?lang=en", { query: { id: 123 } });
```

## ✔️ Interceptors

Providing async interceptors to hook into lifecycle events of `ofetch` call is possible.

You might want to use `ofetch.create` to set shared interceptors.

### `onRequest({ request, options })`

`onRequest` is called as soon as `ofetch` is called, allowing you to modify options or do simple logging.

```js
await ofetch("/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();
  },
});
```

### `onRequestError({ request, options, error })`

`onRequestError` will be called when the fetch request fails.

```js
await ofetch("/api", {
  async onRequestError({ request, options, error }) {
    // Log error
    console.log("[fetch request error]", request, error);
  },
});
```

### `onResponse({ request, options, response })`

`onResponse` will be called after `fetch` call and parsing body.

```js
await ofetch("/api", {
  async onResponse({ request, response, options }) {
    // Log response
    console.log("[fetch response]", request, response.status, response.body);
  },
});
```

### `onResponseError({ request, options, response })`

`onResponseError` is the same as `onResponse` but will be called when fetch happens but `response.ok` is not `true`.

```js
await ofetch("/api", {
  async onResponseError({ request, response, options }) {
    // Log error
    console.log(
      "[fetch response error]",
      request,
      response.status,
      response.body
    );
  },
});
```

### Passing array of interceptors

If necessary, it's also possible to pass an array of function that will be called sequentially.

```js
await ofetch("/api", {
  onRequest: [
    () => {
      /* Do something */
    },
    () => {
      /* Do something else */
    },
  ],
});
```

## ✔️ Create fetch with default options

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 about nested options like `headers`.

```js
const apiFetch = ofetch.create({ baseURL: "/api" });

apiFetch("/test"); // Same as ofetch('/test', { baseURL: '/api' })
```

## 💡 Adding headers

By using `headers` option, `ofetch` adds extra headers in addition to the request default headers:

```js
await ofetch("/movies", {
  headers: {
    Accept: "application/json",
    "Cache-Control": "no-cache",
  },
});
```

## 🍣 Access to Raw Response

If you need to access raw response (for headers, etc), you can use `ofetch.raw`:

```js
const response = await ofetch.raw("/sushi");

// response._data
// response.headers
// ...
```

## 🌿 Using Native Fetch

As a shortcut, you can use `ofetch.native` that provides native `fetch` API

```js
const json = await ofetch.native("/sushi").then((r) => r.json());
```

## 📡 SSE

**Example:** Handle SSE response:

```js
const stream = await ofetch("/sse");
const reader = stream.getReader();
const decoder = new TextDecoder();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  // Here is the chunked text of the SSE response.
  const text = decoder.decode(value);
}
```

## 🕵️ Adding HTTP(S) Agent

In Node.js (>= 18) environments, you can provide a custom dispatcher to intercept requests and support features such as Proxy and self-signed certificates. This feature is enabled by [undici](https://undici.nodejs.org/) built-in Node.js. [read more](https://undici.nodejs.org/#/docs/api/Dispatcher) about the Dispatcher API.

Some available agents:

- `ProxyAgent`: A Proxy Agent class that implements the Agent API. It allows the connection through a proxy in a simple way. ([docs](https://undici.nodejs.org/#/docs/api/ProxyAgent))
- `MockAgent`: A mocked Agent class that implements the Agent API. It allows one to intercept HTTP requests made through undici and return mocked responses instead. ([docs](https://undici.nodejs.org/#/docs/api/MockAgent))
- `Agent`: Agent allows dispatching requests against multiple different origins. ([docs](https://undici.nodejs.org/#/docs/api/Agent))

**Example:** Set a proxy agent for one request:

```ts
import { ProxyAgent } from "undici";
import { ofetch } from "ofetch";

const proxyAgent = new ProxyAgent("http://localhost:3128");
const data = await ofetch("https://icanhazip.com", { dispatcher: proxyAgent });
```

**Example:** Create a custom fetch instance that has proxy enabled:

```ts
import { ProxyAgent, setGlobalDispatcher } from "undici";
import { ofetch } from "ofetch";

const proxyAgent = new ProxyAgent("http://localhost:3128");
const fetchWithProxy = ofetch.create({ dispatcher: proxyAgent });

const data = await fetchWithProxy("https://icanhazip.com");
```

**Example:** Set a proxy agent for all requests:

```ts
import { ProxyAgent, setGlobalDispatcher } from "undici";
import { ofetch } from "ofetch";

const proxyAgent = new ProxyAgent("http://localhost:3128");
setGlobalDispatcher(proxyAgent);

const data = await ofetch("https://icanhazip.com");
```

**Example:** Allow self-signed certificates (USE AT YOUR OWN RISK!)

```ts
import { Agent } from "undici";
import { ofetch } from "ofetch";

// Note: This makes fetch unsecure against MITM attacks. USE AT YOUR OWN RISK!
const unsecureAgent = new Agent({ connect: { rejectUnauthorized: false } });
const unsecureFetch = ofetch.create({ dispatcher: unsecureAgent });

const data = await unsecureFetch("https://www.squid-cache.org/");
```

### 💪 Augment `FetchOptions` interface

You can augment the `FetchOptions` interface to add custom properties.

```ts
// Place this in any `.ts` or `.d.ts` file.
// Ensure it's included in the project's tsconfig.json "files".
declare module "ofetch" {
  interface FetchOptions {
    // Custom properties
    requiresAuth?: boolean;
  }
}

export {};
```

This lets you pass and use those properties with full type safety throughout `ofetch` calls.

```ts
const myFetch = ofetch.create({
  onRequest(context) {
    //      ^? { ..., options: {..., requiresAuth?: boolean }}
    console.log(context.options.requiresAuth);
  },
});

myFetch("/foo", { requiresAuth: true });
```

## License

💛 Published under the [MIT](https://github.com/h3js/rou3/blob/main/LICENSE) license.
