base on 😱 A better fetch API. Works everywhere. # 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. ", Assign "at most 3 tags" to the expected json: {"id":"7891","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"