get-it
Composable HTTP request library for Node.js, browsers, Deno, Bun, and edge runtimes
Repository Health
Technical Analysis
get-it is Sanity’s generic HTTP request client, built directly on the standard fetch() API rather than wrapping a third-party HTTP stack. It supports a wide range of JavaScript runtimes — Node.js (>=22.12), browsers, Deno, Bun, Cloudflare Workers/workerd, Vercel Edge, and React Server Components — dispatching to a runtime-appropriate fetch implementation (an undici-backed fetch with proxy support on Node.js/Bun) through conditional package exports so consumers write one request layer for every target.
Requests are created through createRequester(), which returns a callable request function configured once with a base URL, default headers, JSON serialization, and a composable middleware pipeline of transform middleware (which mutate options/responses) and wrapping middleware (which wrap the fetch call itself, useful for retry and logging). Built-in middleware covers retry and debug logging, dual-phase timeouts (a total deadline plus a separate headers-only phase) integrate with the standard AbortSignal, and a bundled mock-fetch module with Vitest matchers lets consumers test HTTP code without touching the network.
What You Get
- Promise-based createRequester() API with full TypeScript types and per-
asresponse typing (json/text/stream) - Automatic JSON body serialization and parsing, plus streaming response bodies via ReadableStream
- Dual-phase timeout handling (total deadline + headers-only phase) built on the standard AbortSignal/AbortController
- A composable middleware system (transform + wrapping middleware) with built-in retry() and debug() middleware
- Runtime-specific fetch dispatch, including an undici-backed Node.js/Bun fetch with HTTP/HTTPS proxy and mTLS support
- A built-in mock-fetch module (get-it/mock) with request matching, recording, and custom Vitest matchers
Common Use Cases
- Building a typed internal SDK/client for a REST API that needs to run in both Node.js backends and browser/edge frontends from one codebase
- Adding resilient retry and structured debug logging to outbound HTTP calls without hand-rolling fetch wrappers
- Writing unit tests for HTTP-calling code with no live network, using get-it/mock’s request matchers and Vitest integration
- Configuring proxy and mutual-TLS settings for HTTP clients running in Node.js/Bun server environments (e.g. behind a corporate proxy)
Under The Hood
Architecture
get-it’s core lives in a single createRequester() factory (src/createRequester.ts) that composes several layers rather than delegating to a class hierarchy: a public request() dispatcher picks a per-call handler (requestJson, requestText, requestStream) based on the as option, each of which runs beforeRequest transform middleware, invokes a composed fetchChain of wrapping middleware (built via composeFetchChain, applied outermost-to-innermost), and finishes with afterResponse transforms. The innermost link, getItBuffered, delegates to performFetch, which owns the timeout/AbortSignal orchestration (racing a rejection-only deadline against the fetch, or attaching an AbortController-derived signal) before handing the raw response to bufferAndCheck for buffering and HttpError throwing. Runtime-specific behavior is isolated outside this core: createNodeFetch.ts adapts undici’s Agent/ProxyAgent/EnvHttpProxyAgent into the shared FetchFunction interface, and conditional package.json exports (node/deno/workerd/worker/react-server/default) select the right entry point per runtime without branching inside the request logic itself. Swapping the core fetch strategy touches performFetch and buildFetchArgs; swapping a runtime’s transport touches only its adapter.
Tech Stack The library is authored in TypeScript (6.0.3) and builds to distributable ESM output with tsdown, targeting Node.js >=22.12 alongside browsers, Deno, Bun, and edge runtimes (workerd, Vercel Edge, React Server Components). Its only runtime dependency is undici, used exclusively for the Node/Bun fetch adapter that provides HTTP/HTTPS proxy support (EnvHttpProxyAgent, ProxyAgent) and mutual-TLS configuration; every other capability — middleware, mocking, timeouts — is implemented directly against the standard fetch()/Headers/AbortController APIs with no additional runtime dependencies. Testing runs on Vitest 4 with per-runtime configs (@vitest/browser with Playwright, happy-dom, jsdom, @cloudflare/vitest-pool-workers for workerd, @edge-runtime/vm for Vercel Edge), and the toolchain uses oxlint and oxfmt (Rust-based lint/format) plus knip for dead-code detection, changesets for versioned releases, and a bundle-size-tracking script enforced through CI.
Code Quality
Code quality is enforced unusually strictly for a small library: CONTRIBUTING.md bans type assertions outright (no as, <Type>, or as any anywhere, including tests) and requires narrowing via typeof/instanceof/discriminated unions or hand-written type guards instead, a rule the source code (isHttpError, isTimeoutError, isPlainObject, isBinaryBody) visibly follows. The test suite spans 34 files covering not just unit behavior but also multi-runtime smoke tests, with a test:all script that runs the full suite against Node.js, Bun, Deno, workerd, Vercel Edge, React Server Components, and happy-dom to catch runtime-specific fetch behavior differences. Errors are modeled as explicit typed classes (HttpError, TimeoutError) rather than generic throws, with dedicated stack-trace stripping (Error.captureStackTrace) to keep traces focused on caller code. CI (a dedicated workflow plus separate format, browserslist-compatibility, and bundle-stats workflows) gates merges on lint, typecheck, formatting, and multi-runtime test runs.
API Design
get-it’s public API favors a few deliberate, non-obvious choices over convention. Timeouts are split into a total deadline and a separate headers-only phase, both expressed through the standard AbortSignal/AbortController rather than a bespoke cancellation token, with an explicit signal: false escape hatch added specifically to keep Next.js fetch request memoization intact when a caller can’t attach an abort signal. Middleware is split into two distinct shapes — plain-object transform middleware for mutating options/responses, and function-based wrapping middleware for logic that needs to see the whole fetch call (like the built-in retry()) — which keeps stack traces meaningful for the latter while keeping the former lightweight. Rather than depending on an external mocking library, get-it ships its own dependency-free mock fetch (get-it/mock) with method/URL/query/body/header matching, one-shot vs persistent handlers, request recording, and diff-based errors on unmatched requests, plus first-class Vitest matchers (get-it/vitest) — giving consumers a fully self-contained testing story.