A tiny (~1.8KB g-zipped) wrapper built around fetch with an intuitive syntax.
f[ETCH] [WR]apper
##### Wretch 3.0 is now live 🎉 ! Check out the [Migration Guide](MIGRATION_V2_V3.md) for upgrading from v2, and please have a look at the [releases](https://github.com/elbywan/wretch/releases) and the [changelog](https://github.com/elbywan/wretch/blob/master/CHANGELOG.md) after each update for new features and breaking changes.
##### And if you like the library please consider becoming a [sponsor](https://github.com/sponsors/elbywan) ❤️.
# Features
#### `wretch` is a small wrapper around [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) designed to simplify the way to perform network requests and handle responses.
- 🪶 **Small** - core is less than 1.8KB g-zipped
- 💡 **Intuitive** - lean API, handles errors, headers and (de)serialization
- 🧊 **Immutable** - every call creates a cloned instance that can then be reused safely
- 🔌 **Modular** - plug addons to add new features, and middlewares to intercept requests
- 🧩 **Isomorphic** - compatible with modern browsers, Node.js 22+, Deno and Bun
- 🦺 **Type safe** - strongly typed, written in TypeScript
- ✅ **Proven** - fully covered by unit tests and widely used
- 💓 **Maintained** - alive and well for many years
# Table of Contents
- [**Quick Start**](#quick-start)
- [**Motivation**](#motivation)
- [**Installation**](#installation)
- [**Compatibility**](#compatibility)
- [**Usage**](#usage)
- [**Recipes**](#recipes)
- [**Api**](#api-)
- [**Addons**](#addons)
- [**Middlewares**](#middlewares)
- [**Migration Guides**](#migration-guides)
- [**License**](#license)
# Quick Start
# 1️⃣ Install
npm i wretch
// 2️⃣ Import and create a reusable API client
import wretch from "wretch"
const api = wretch("https://jsonplaceholder.typicode.com")
.options({ mode: "cors" })
// 3️⃣ Make requests with automatic JSON handling
const post = await api.get("/posts/1").json()
console.log(post.title)
// 4️⃣ POST with automatic serialization
const created = await api
.post({ title: "New Post", body: "Content", userId: 1 }, "/posts")
.json()
// 5️⃣ Handle errors elegantly
await api
.get("/posts/999")
.notFound(() => console.log("Post not found!"))
.json()
// 6️⃣ Different response types
const text = await api.get("/posts/1").text() // Raw text
const response = await api.get("/posts/1").res() // Raw Response object
const blob = await api.get("/photos/1").blob() // Binary data
# Motivation
#### Because having to write a second callback to process a response body feels awkward.
Fetch needs a second callback to process the response body.
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(json => {
//Do stuff with the parsed json
});
Wretch does it for you.
// Use .res for the raw response, .text for raw text, .json for json, .blob for a blob …
wretch("https://jsonplaceholder.typicode.com/posts/1")
.get()
.json(json => {
// Do stuff with the parsed json
return json
});
#### Because manually checking and throwing every request error code is tedious.
Fetch won't reject on HTTP error status.
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => {
if(!response.ok) {
if(response.status === 404) throw new Error("Not found")
else if(response.status === 401) throw new Error("Unauthorized")
else if(response.status === 418) throw new Error("I'm a teapot !")
else throw new Error("Other error")
}
else {/* … */}
})
.then(data => {/* … */})
.catch(error => { /* … */ })
Wretch throws when the response is not successful and contains helper methods to handle common codes.
wretch("https://jsonplaceholder.typicode.com/posts/1")
.get()
.notFound(error => { /* … */ })
.unauthorized(error => { /* … */ })
.error(418, error => { /* … */ })
.res(response => {/* … */ })
.catch(error => { /* uncaught errors */ })
#### Because sending a json object should be easy.
With fetch you have to set the header, the method and the body manually.
fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ "hello": "world" })
}).then(response => {/* … */})
// Omitting the data retrieval and error management parts…
With wretch, you have shorthands at your disposal.
wretch("https://jsonplaceholder.typicode.com/posts")
.post({ "hello": "world" })
.res(response => { /* … */ })
#### Because configuration should not rhyme with repetition.
A Wretch object is immutable which means that you can reuse previous instances safely.
const token = "MY_SECRET_TOKEN"
// Cross origin authenticated requests on an external API
const externalApi = wretch("https://jsonplaceholder.typicode.com") // Base url
// Authorization header
.auth(`Bearer ${token}`)
// Cors fetch options
.options({ credentials: "include", mode: "cors" })
// Handle 403 errors
.resolve((w) => w.forbidden(error => { /* Handle all 403 errors */ }));
// Fetch a resource
const resource = await externalApi
// Add a custom header for this request
.headers({ "If-Unmodified-Since": "Wed, 21 Oct 2015 07:28:00 GMT" })
.get("/posts/1")
.json(() => {/* do something with the resource */});
// Post a resource
externalApi
.url("/posts")
.post({ "Shiny new": "resource object" })
.json(() => {/* do something with the created resource */});
# Installation
## Package Manager
npm i wretch # or yarn/pnpm add wretch
## <script> tag
The package contains multiple bundles depending on the format and feature set located under the `/dist/bundle` folder.
Bundle variants
> 💡 If you pick the core bundle, then to plug addons you must import them separately from `/dist/bundle/addons/[addonName].min.js`
| Feature set | File Name |
| ------------------ | ------------------- |
| Core features only | `wretch.min.js` |
| Core + all addons | `wretch.all.min.js` |
| Format | Extension |
| -------- | ---------- |
| ESM | `.min.mjs` |
| CommonJS | `.min.cjs` |
| UMD | `.min.js` |
<script src="https://unpkg.com/wretch"></script>
<script type="module">
import wretch from 'https://cdn.skypack.dev/wretch/dist/bundle/wretch.all.min.mjs'
// … //
</script>
# Compatibility
## Browsers
`wretch@^3` is compatible with modern browsers only. For older browsers please use `wretch@^1`.
## Node.js
Wretch is compatible with and tested in _Node.js >= 22_.
For older Node.js versions, please use `wretch@^2`.
Node.js 22+ includes native fetch support and all required Web APIs (FormData, URLSearchParams, AbortController, etc.) out of the box, so no polyfills are needed.
## Deno
Works with [Deno](https://deno.land/) out of the box.
deno add npm:wretch
import wretch from "wretch";
const text = await wretch("https://httpbingo.org").get("/status/200").text();
console.log(text); // -> { "code": 200, "description": "OK" }
## Bun
Works with [Bun](https://bun.sh/) out of the box.
bun add wretch
import wretch from "wretch";
const text = await wretch("https://httpbingo.org").get("/status/200").text();
console.log(text); // -> { "code": 200, "description": "OK" }
# Usage
## Import
// ECMAScript modules
import wretch from "wretch"
// CommonJS
const wretch = require("wretch")
// Global variable (script tag)
window.wretch
## Common Use Cases
// 🌐 REST API Client
const api = wretch("https://jsonplaceholder.typicode.com")
.auth("Bearer token")
.resolve(r => r.json())
const users = await api.get("/users")
const user = await api.post({ name: "John" }, "/users")
users
// 📤 File Upload with Progress
import ProgressAddon from "wretch/addons/progress"
import FormDataAddon from "wretch/addons/formData"
await wretch("https://httpbingo.org/post")
.addon([FormDataAddon, ProgressAddon()])
.formData({ file: file })
.post()
.progress((loaded, total) => console.log(`${(loaded/total*100).toFixed()}%`))
.json()
// 🔄 Auto-retry on Network Failure
import { retry } from "wretch/middlewares"
const resilientApi = wretch()
.middlewares([retry({ maxAttempts: 3, retryOnNetworkError: true })])
// 🎯 Type-safe TypeScript
interface User { id: number; name: string; email: string }
const user = await wretch("https://jsonplaceholder.typicode.com")
.get("/users/1")
.json<User>() // Fully typed!
user
// 🔐 Automatic Token Refresh
const api = wretch("https://httpbingo.org/basic-auth/user/pass")
.addon(BasicAuthAddon)
.resolve(w => w.unauthorized(async (error, req) => {
const newToken = await refreshToken()
return req
.basicAuth("user", "pass")
.unauthorized(e => {
console.log("Still unauthorized after token refresh");
throw e
})
.fetch()
.json()
}))
## Custom Fetch Implementation
You can provide a custom fetch implementation using the `.fetchPolyfill()` method. This is useful for for a variety of use cases including mocking, adding logging, timing, or other custom behavior to all requests made through a wretch instance.
import wretch from "wretch"
// Per-instance custom fetch
const api = wretch("https://jsonplaceholder.typicode.com")
.fetchPolyfill((url, opts) => {
console.log('Fetching:', url)
console.time(url)
return fetch(url, opts).finally(() => {
console.timeEnd(url)
})
})
await api.get("/posts").json()
### [toFetch() - Fetch Adapter 🔗](https://elbywan.github.io/wretch/api/interfaces/index.Wretch#tofetch)
Converts a wretch instance into a fetch-like function, preserving all configuration (middlewares, catchers, headers, etc.). Useful for integrating wretch with libraries that expect a fetch signature.
const myFetch = wretch("https://jsonplaceholder.typicode.com")
.auth("Bearer token")
.catcher(401, (err) => console.log("Unauthorized"))
.toFetch()
// Use like regular fetch
const response = await myFetch("/users", { method: "GET" })
response
// Pass to libraries expecting fetch
import { createClient } from "@apollo/client"
const client = createClient({
fetch: wretch().auth("Bearer token").toFetch()
})
## Chaining
**A high level overview of the successive steps that can be chained to perform a request and parse the result.**
```
┌─────────────────────────────────────────────────────────────────┐
│