Skip to content

unjs/ofetch

Repository files navigation

ofetch

npm version npm downloads

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

Important

You are on v2 (alpha) development branch. See v1 for v1 docs.

Spoiler

πŸš€ Quick Start

Install:

npx nypm i ofetch

Import:

import { ofetch } from "ofetch";

βœ”οΈ Parsing Response

ofetch smartly parse JSON responses.

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.

// 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. ofetch will automatically set the duplex: "half" option for streaming support!

Example:

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.

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:

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

To bypass status error catching you can set ignoreResponseError option:

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)
  • 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.

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).

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

βœ”οΈ Type Friendly

The response can be type assisted:

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:

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:

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.

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.

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.

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.

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.

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.

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:

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:

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

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

πŸ“‘ SSE

Example: Handle SSE response:

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 built-in Node.js. read more 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)
  • 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)
  • Agent: Agent allows dispatching requests against multiple different origins. (docs)

Example: Set a proxy agent for one request:

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:

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:

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!)

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.

// 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.

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

License

πŸ’› Published under the MIT license.