npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@clement_lores/quick-fetch

v1.0.0

Published

A modern, fetch-based HTTP client: zero-dependency, typed, minimal, and secure by default.

Downloads

91

Readme

quick-fetch

quick-fetch is a zero-dependency, TypeScript-first HTTP client built on native fetch.

It keeps everything you like about fetch, while removing repetitive boilerplate for day-to-day REST API calls.

Why quick-fetch

Native fetch is powerful, but most API layers still repeat the same plumbing:

  • Building URLs and query strings
  • Handling JSON request/response bodies
  • Managing per-request timeouts
  • Throwing structured errors for non-2xx responses
  • Repeating headers and base URL setup

quick-fetch gives you a clean, minimal abstraction without introducing heavy dependencies or a custom transport layer.

Highlights

  • ZERO dependencies
  • TypeScript-first API
  • ESM + CommonJS support
  • Built on native fetch, AbortController, URL, and FormData
  • Automatic response parsing (JSON, text, and 204 handling)
  • Structured HttpError for non-2xx responses
  • Built-in timeout support with TimeoutError
  • Safe guardrail for invalid body options (json + formdata)
  • Base URL, global headers, and per-request override support
  • Logging modes: basic, detailed, full

Install

npm:

npm install @clement_lores/quick-fetch

Runtime Support

quick-fetch works in modern runtimes that provide native web APIs used by fetch-based clients:

  • Browsers (modern)
  • Node.js 18+ (recommended)
  • Deno
  • Bun
  • Edge runtimes with fetch-compatible APIs

No additional dependency is required by quick-fetch itself.

Quick Start

import { createClient } from "quick-fetch";

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

const api = createClient({
	baseUrl: "https://api.example.com",
	headers: {
		Authorization: "Bearer <token>"
	},
	timeout: 8000,
	logging: "basic"
});

const users = await api.get<User[]>("/users", {
	query: {
		page: 1,
		search: "john"
	}
});

API Overview

createClient(options?)

Creates a reusable HTTP client instance.

import { createClient } from "quick-fetch";

const api = createClient({
	baseUrl: "https://api.example.com",
	headers: { "x-app": "my-service" },
	timeout: 5000,
	logging: "detailed"
});

ClientOptions

type ClientOptions = {
	baseUrl?: string;
	headers?: HeadersInit;
	timeout?: number;
	logging?: "basic" | "detailed" | "full";
};

Option details:

  • baseUrl: Base URL used with relative request paths. Strongly recommended in production.
  • headers: Default headers applied to every request.
  • timeout: Default timeout in milliseconds for all requests.
  • logging:
  • basic: Logs request method/path and body type.
  • detailed: Logs request details and body payload overview.
  • full: Adds response status and duration.

Client Methods

All methods are generic and return Promise.

api.get<TResponse>(path, options?)
api.post<TResponse>(path, options?)
api.put<TResponse>(path, options?)
api.delete<TResponse>(path, options?)
api.patch<TResponse>(path, options?)
api.head<TResponse>(path, options?)
api.options<TResponse>(path, options?)

RequestOptions

type RequestOptions = {
	headers?: HeadersInit;
	query?: Record<string, QueryParamValue | QueryParamValue[]>;
	json?: unknown;
	formdata?: FormData;
	timeout?: number;
	signal?: AbortSignal;
};

type QueryParamValue = string | number | boolean | null | undefined;

Rule:

  • Use either json or formdata, never both in one request.

If both are provided, quick-fetch throws MalformedParamsError.

REST Examples (TypeScript)

GET with Query Parameters

type ProductsResponse = {
	items: Array<{ id: string; name: string }>;
	total: number;
};

const products = await api.get<ProductsResponse>("/products", {
	query: {
		page: 1,
		search: "helmet",
		available: true,
		brand: ["Honda", "Yamaha"],
		unused: undefined,
		nullable: null
	}
});

Behavior:

  • Arrays are expanded as repeated query keys.
  • null and undefined values are skipped.

POST JSON Body

type CreateUserPayload = {
	name: string;
	email: string;
};

type CreateUserResponse = {
	id: string;
	name: string;
	email: string;
};

const created = await api.post<CreateUserResponse>("/users", {
	json: {
		name: "Ada Lovelace",
		email: "[email protected]"
	} satisfies CreateUserPayload
});

When json is provided:

  • Request body is JSON.stringify(json).
  • content-type: application/json is set automatically.

POST FormData (File Upload)

const form = new FormData();
form.append("avatar", fileInput.files?.[0] as File);
form.append("displayName", "Ada");

const result = await api.post<{ ok: boolean }>("/profile/avatar", {
	formdata: form
});

When formdata is provided:

  • Body is sent as multipart/form-data.
  • content-type boundary is handled by runtime automatically.

Per-request Timeout Override

const slowReport = await api.get<{ status: string }>("/reports/daily", {
	timeout: 15000
});

Manual Cancellation

const controller = new AbortController();

const pending = api.get<{ ok: true }>("/long-task", {
	signal: controller.signal
});

controller.abort();

await pending;

Error Handling

quick-fetch throws explicit, typed errors for common API failure modes.

Error Types

HttpError

Thrown for all non-2xx responses.

Properties:

  • name: HttpError
  • status: number
  • statusText: string
  • url: string
  • method: HTTP method
  • body: Parsed response body (JSON or text)

TimeoutError

Thrown when request timeout is reached.

MalformedParamsError

Thrown when both json and formdata are provided in the same request.

Handling Errors Safely

import { HttpError, TimeoutError, MalformedParamsError } from "quick-fetch";

try {
	const data = await api.get<{ id: string }>("/users/unknown");
	console.log(data);
} catch (error) {
	if (error instanceof HttpError) {
		console.error("HTTP failure", {
			status: error.status,
			statusText: error.statusText,
			method: error.method,
			url: error.url,
			body: error.body
		});
	} else if (error instanceof TimeoutError) {
		console.error("Request timeout", error.message);
	} else if (error instanceof MalformedParamsError) {
		console.error("Request options are invalid", error.message);
	} else {
		console.error("Unexpected error", error);
	}
}

Important behavior note:

  • Timeout-triggered aborts become TimeoutError.
  • Manual AbortSignal cancellation uses the native AbortError from fetch/runtime.

Response Parsing

quick-fetch parses response bodies automatically:

  • Status 204: returns undefined
  • content-type includes application/json: returns parsed JSON
  • Otherwise: returns text

This applies to both successful and error responses (HttpError.body is parsed too).

Logging Modes

Set logging in createClient:

const api = createClient({
	baseUrl: "https://api.example.com",
	logging: "full"
});

Modes:

  • basic: Logs request method/path and body type indicator.
  • detailed: Adds request payload details for easier debugging.
  • full: Includes detailed request data plus colored response status and duration.

Header Strategy

quick-fetch merges headers like this:

  • Start from client-level headers
  • Apply request-level headers on top
  • Request-level values override duplicates

Example:

const api = createClient({
	baseUrl: "https://api.example.com",
	headers: {
		Authorization: "Bearer token-1",
		"x-app": "dashboard"
	}
});

await api.get("/users", {
	headers: {
		Authorization: "Bearer token-2"
	}
});

Best Practices

  • Always provide baseUrl for production clients.
  • Type every response with method generics.
  • Centralize HTTP error handling around HttpError.
  • Use client-level headers for stable defaults.
  • Use request-level timeout for known slow endpoints.
  • Keep logging to basic or detailed in production unless actively debugging.

Comparison

quick-fetch vs native fetch

  • quick-fetch adds typed method wrappers for REST calls.
  • quick-fetch gives structured HttpError for non-2xx responses.
  • quick-fetch auto-parses JSON/text and handles 204 gracefully.
  • quick-fetch supports built-in timeout handling and query object serialization.
  • native fetch remains the underlying transport layer.

quick-fetch vs axios

  • quick-fetch has zero dependencies.
  • quick-fetch stays close to native fetch standards.
  • quick-fetch is intentionally minimal and focused on REST request ergonomics.
  • axios provides a broader feature set (interceptors, richer transforms, etc.).

Current Scope and Limitations

quick-fetch intentionally stays small and focused.

Not included by design in current stable scope:

  • Automatic retries
  • Interceptors/middleware pipeline
  • Built-in request/response schema validation
  • Built-in caching strategy
  • GraphQL-specific helpers

Stability and Versioning

  • API stability target: stable
  • Versioning strategy: semantic versioning (SemVer)

FAQ

Does quick-fetch replace fetch?

No. It builds on top of native fetch and keeps fetch semantics.

Can I use absolute URLs without baseUrl?

Yes. baseUrl is optional. However, using baseUrl is recommended for consistency and maintainability.

Does quick-fetch parse error responses?

Yes. Non-2xx responses throw HttpError with a parsed body field when possible.

What happens with HEAD requests?

A 204 response returns undefined. For non-JSON textual responses, text is returned.

Is this package browser-only?

No. It is runtime-agnostic as long as native fetch-compatible APIs are available.

License

MIT

Copyright (c) 2026 Clément LORES

Author

GitHub: https://github.com/loresclement

Repository

https://github.com/loresclement/quick-fetch