ofetch
A better fetch API for Node, browsers, and workers with JSON parsing, retries, and typed errors built in.
Repository Health
Technical Analysis
ofetch wraps the native fetch API to remove the boilerplate that every project ends up rewriting by hand: it parses JSON responses automatically (falling back to blob/text/stream when the content type calls for it), stringifies JSON request bodies with sensible default headers, and throws a normalized FetchError with a readable message and a compact stack trace whenever a response isn’t OK. Failed requests to a configurable set of status codes (408, 409, 425, 429, 500, 502, 503, 504) are retried automatically, with support for custom retry counts, delays, and status code lists.
Beyond the request/response basics, ofetch adds baseURL and query/params merging via unjs/ufo, request/response/error lifecycle hooks (onRequest, onRequestError, onResponse, onResponseError), timeout support via AbortSignal, and an ofetch.create() factory for building a preconfigured instance that several calls can share. It runs unmodified across Node.js, browsers, and edge/worker runtimes, and exposes ofetch.raw and ofetch.native for callers who need the underlying Response object or an untouched native fetch.
As part of the unjs ecosystem, ofetch is the HTTP client used inside Nuxt, Nitro, and a number of other unjs packages, making it a common dependency for anyone building on those frameworks even when they never call it directly.
What You Get
- Automatic response parsing that picks JSON, blob, text, or stream based on the response’s content type, with an optional custom
parseResponsefunction. - Automatic JSON request body stringification, with the correct
content-type/acceptheaders set only when they aren’t already present. - Built-in retry logic for a configurable list of status codes, with
retry,retryDelay(static or a function of the request context), and safe defaults that avoid retrying payload methods unless explicitly opted in. - Normalized
FetchErrorobjects carryingdata,status,statusText,request, andoptions, with internal stack frames hidden for a cleaner trace. - Lifecycle hooks —
onRequest,onRequestError,onResponse,onResponseError— that can be single functions or arrays, run sequentially. baseURLandquery/paramsmerging handled through unjs/ufo so relative paths and query strings compose correctly.ofetch.create()for producing a pre-configured client with shared defaults, andofetch.raw/ofetch.nativefor lower-level access.
Common Use Cases
- Calling a JSON REST API from a Node.js server or serverless/edge function without hand-writing response parsing and error handling.
- Building a typed API client wrapper (
ofetch.create({ baseURL })) shared across many call sites in an app. - Adding automatic retry-with-backoff behavior for flaky upstream services without a separate retry library.
- Streaming or handling binary responses (images, SSE, file downloads) where the default JSON assumption would be wrong.
- Powering the HTTP layer inside a Nuxt/Nitro application, since ofetch is the default fetch client for both.
Under The Hood
Architecture
The package is deliberately small and split by concern: src/fetch.ts implements createFetch(), which closes over a fetch implementation and returns the $fetch/ofetch callable plus .raw, .native, and .create methods; src/utils.ts holds the pure option-resolution and header-merging logic (resolveFetchOptions, mergeHeaders, callHooks); src/utils.url.ts handles baseURL/query composition via ufo; and src/error.ts builds the normalized FetchError using Object.defineProperty getters so error.data/error.status always reflect the live response/context rather than a stale copy. src/index.ts is the thin public entry point that wires a global-scope fetch into createFetch. The core request path in $fetchRaw is a single async function that threads a shared FetchContext (request, options, response, error) through hook calls, body normalization, the underlying fetch() call, response parsing, and error handling — every extension point (retry, hooks, error shape) is a branch over that one context object rather than a separate abstraction layer, so the whole request lifecycle is traceable in one file.
Tech Stack
Written in TypeScript targeting ESM only (type: module, single ./dist/index.mjs entry), built with obuild, and published without runtime dependencies beyond ufo (used for URL/query joining) — Node’s undici and node-fetch-native are referenced only as optional/dev-time types for the dispatcher/agent options, not hard dependencies. Linting is via ESLint using eslint-config-unjs plus Prettier for formatting, and changelogen drives release/versioning. The library targets Node.js 18+, modern browsers, and edge/worker runtimes that implement the standard Fetch API, with no bundler-specific tooling required to consume it.
Code Quality
Tests live in a single test/index.test.ts file run with Vitest (vitest run --coverage, using @vitest/coverage-v8), covering JSON/body handling, retries, timeouts, hooks, and error shapes against a real h3-based test server rather than mocks. Error handling is explicit throughout — createFetchError centralizes error construction instead of scattering throw statements, and the retry path distinguishes abort errors from retryable status codes rather than swallowing failures. Naming is consistent and the codebase is fully typed with no any escape hatches in the public API surface; CI runs lint, build, and the test suite with coverage across three Node versions (20/22/24) on every push and PR.
API Design
The public surface is intentionally minimal: ofetch(url, options) behaves like fetch but resolves directly to parsed data, ofetch.raw returns the full response when headers/status are needed, ofetch.create() bakes in shared defaults for reuse across a codebase, and ofetch.native provides an escape hatch to unmodified fetch. Getting started requires no configuration — await ofetch('/api/users') is a complete example — and the options object composes gradually (baseURL, query, retry, timeout, hooks) rather than requiring a builder or class instantiation, which keeps the learning curve low for anyone already familiar with fetch.
Used by 7 apps in this directory
Directus
CMS · Low Code Platforms
Connect any SQL database and get instant REST and GraphQL APIs, a visual management Studio, and a native MCP server for AI agents — free for most organizations.
Enclosed
Security
Send end-to-end encrypted notes and files where the server never sees your content — true zero-knowledge sharing.
NewsNow
Bookmarks Archiving
Self-hostable news aggregator with an elegant column layout, GitHub OAuth, adaptive scraping intervals, and seamless Cloudflare D1 or SQLite persistence.
open-pencil
AI Design Tools · Design Tools
An open-source design editor that reads native Figma files, ships a built-in AI assistant with 100+ design tools, and offers real-time serverless collaboration — all without giving up your files.
Papra
Bookmarks Archiving
Self-hosted document archiving with email ingestion, OCR full-text search, and pluggable storage — store once, find anything.
RSSHub
Automation · Social Media
Turn any website into an RSS feed — social media, streaming platforms, and niche sites all become subscribable in seconds.
Unlighthouse
Developer Tools
An open-source CLI that scans your entire website with Google Lighthouse, using smart sampling and a modern dashboard UI to audit every page instead of one URL at a time.