@poluru-labs/fetchwise

Type-safe Fetch HTTP client with retries, interceptors, validation, and multi-host baseURL. Published as @poluru-labs/fetchwise; not affiliated with the npm package fetchwise.
1,102 2
Install
npm install @poluru-labs/fetchwise
Latest Version:0.1.0
License:MIT
Last Updated:Oct 1, 2026
Links: GitHub  ·  npm
Maintainer: poluru

@poluru-labs/fetchwise

A small, type-safe HTTP client for TypeScript and JavaScript. Built on the Fetch API.

This package is not affiliated with the existing npm package fetchwise. Install this one as @poluru-labs/fetchwise.

Works in any JS environment — browsers, Node, Deno, Bun, React Native, and frameworks like React, Vue, Angular, Lit, Next.js, and Nuxt.

Automatic retries · Response validation · Interceptors · Generated API types · Multiple API hosts

import { createClient } from "@poluru-labs/fetchwise";

const api = createClient({
  baseURL: {
    default: "https://api.shop.com",
    auth: "https://auth.shop.com",
  },
  retry: 3,
});

const users = await api.get("/users");
const session = await api.get("/session", { baseURL: "auth" });

Install

npm install @poluru-labs/fetchwise
pnpm add @poluru-labs/fetchwise
yarn add @poluru-labs/fetchwise
bun add @poluru-labs/fetchwise

The unscoped name fetchwise is already taken on npm, so this package is published as @poluru-labs/fetchwise.

Works everywhere

Environment How to import
ESM / TypeScript import { createClient } from "@poluru-labs/fetchwise"
CommonJS const { createClient } = require("@poluru-labs/fetchwise")
Default import import fetchwise from "@poluru-labs/fetchwise"
Browser <script> fetchwise.createClient(...) via unpkg / jsDelivr
React, Vue, Angular, Lit Same ESM import — no adapter
Next.js, Nuxt, SvelteKit, Remix Same import on server and client
Deno / Bun / Workers Same ESM import

The core library has no Node-only APIs. It uses globalThis.fetch, so it runs anywhere Fetch exists.

<script src="https://unpkg.com/@poluru-labs/fetchwise"></script>
<script>
  const api = fetchwise.createClient({ baseURL: "https://api.shop.com" });
  api.get("/users").then(console.log);
</script>

Examples by stack: examples/ — TypeScript, JavaScript, React, Vue, Angular, and Lit.

Quick start

import { createClient } from "@poluru-labs/fetchwise";

type User = { id: number; name: string; email: string };

const api = createClient({
  baseURL: "https://api.shop.com",
  headers: { Accept: "application/json" },
  credentials: "include",
  timeout: 8_000,
  retry: { attempts: 3, delay: 300 },
});

const users = await api.get<User[]>("/users");
const user = await api.get<User>("/users/:id", { params: { id: 1 } });
const created = await api.post<User>("/users", {
  name: "Poluru Ada",
  email: "poluru.ada@shop.com",
});

Plain JavaScript is the same API. See examples/javascript.

Features

Automatic retries

Failed network calls, timeouts, and selected HTTP statuses are retried with backoff.

const api = createClient({
  baseURL: "https://api.example.com",
  retry: {
    attempts: 3,
    delay: 300,
    backoff: "exponential",
    retryOn: [408, 429, 500, 502, 503, 504],
  },
});

Pass retry: 3 for defaults, or retry: false to turn retries off.

Multiple API hosts

baseURL can be one host or a map of names. Pick a host per request, pass a full URL, or extend() a client for one API.

const api = createClient({
  baseURL: {
    default: "https://api.shop.com",
    auth: "https://auth.shop.com",
    payments: "https://payments.shop.com",
  },
});

await api.get("/users");
await api.get("/session", { baseURL: "auth" });
await api.get("/charges", { baseURL: "payments" });
await api.get("/health", { baseURL: "https://status.shop.com" });
await api.get("https://edge.shop.com/ping");

const payments = api.extend({ baseURL: { default: "https://payments.shop.com" } });
await payments.post("/charges", { amount: 2500 });

Response validation

Pass any schema with a parse() method. Zod, Valibot, ArkType, or a tiny custom object all work.

const UserSchema = {
  parse(data: unknown): User {
    return data as User;
  },
};

const user = await api.get("/users/1", { schema: UserSchema });

Invalid payloads throw ValidationError.

Request and response interceptors

api.interceptors.request.use((request) => {
  request.headers.set("Authorization", `Bearer ${token}`);
  return request;
});

api.interceptors.response.use((response) => {
  console.log(response.status, response.url);
  return response;
});

api.interceptors.error.use((error) => {
  if (error.status === 401) logout();
  return error;
});

Generated API types

Describe routes once. fetchwise infers params, bodies, and responses.

interface API {
  "GET /users": User[];
  "GET /users/:id": User;
  "POST /users": { body: CreateUser; response: User };
}

const api = createClient<API>({ baseURL: "https://api.shop.com" });

const users = await api.get("/users");
const user = await api.get("/users/:id", { params: { id: 1 } });
const created = await api.post("/users", { name: "Poluru Ada", email: "poluru.ada@shop.com" });

Or generate that interface from JSON:

npx @poluru-labs/fetchwise generate api.spec.json -o api.types.ts
{
  "name": "API",
  "models": {
    "User": "{ id: number; name: string; email: string }"
  },
  "routes": [
    { "method": "GET", "path": "/users", "response": "User[]" },
    { "method": "POST", "path": "/users", "body": "CreateUser", "response": "User" }
  ]
}

Named methods are available too:

import { defineApi } from "@poluru-labs/fetchwise";

const users = defineApi({
  client: api,
  routes: {
    getUser: { method: "GET", path: "/users/:id" },
    createUser: { method: "POST", path: "/users" },
  },
});

await users.getUser({ params: { id: 1 } });

Examples

Organized by stack in examples/:

Folder Pattern
typescript Generics, retries, generated types
javascript ESM, CommonJS, browser script
react Shared client + useUsers hook
vue Composable + SFCs
angular ApiService + signals
lit LitElement custom elements
npm install
npm run example examples/typescript/basic.ts

API

createClient(options)

Option Type Default Description
baseURL string | { default?: string; [name: string]: string } Default host, or named hosts for multiple APIs
headers HeadersInit Default headers
credentials RequestCredentials Browser CORS cookies (include, same-origin)
timeout number Request timeout in ms
retry number | RetryOptions | false false Retry policy
fetch typeof fetch globalThis.fetch Custom fetch implementation

fetchwise(options) and the default export are aliases of createClient(options).

Methods

api.get(url, config?)
api.post(url, body?, config?)
api.put(url, body?, config?)
api.patch(url, body?, config?)
api.delete(url, config?)
api.request(url, config?)   // returns parsed data
api.send(url, config?)      // returns { data, status, headers, raw }
api.extend(options)         // copy the client with new defaults

Request config

{
  baseURL, // named host or full URL for this request
  headers, query, params, body,
  timeout, retry, schema, signal,
  parseAs: "json" | "text" | "blob" | "auto" | "raw",
  fetchOptions, // extra Fetch init
}

Path tokens like /users/:id are filled from params.

Errors

Class When
HTTPError Response status is not 2xx
TimeoutError The request exceeded timeout
ValidationError schema.parse() failed
FetchwiseError Network, abort, or other failures
import { HTTPError } from "@poluru-labs/fetchwise";

try {
  await api.get("/missing");
} catch (error) {
  if (error instanceof HTTPError || error?.name === "HTTPError") {
    console.log(error.status, error.data);
  }
}

Use error.name when multiple copies of the package are bundled.

License

MIT

Related Packages

@propeller-commerce/propeller-sdk-v2

TypeScript GraphQL client for Propeller eCommerce V2 Platform - works with React...

8,078 1
guito

Easily explore your git repositories.

4,541 1
@hey-api/nuxt

🚀 Nuxt module for `@hey-api/openapi-ts` codegen.

71,945 5,513
@coderefine/cli

CodeRefine — Multi-framework tech debt scanner CLI for React, Vue, Svelte, and A...

2,039
alova

The request strategy layer for JavaScript. 20+ ready-made strategies cut your re...

423,897 4,022

Version History

Version Released License
0.1.0 MIT