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

reqcraft

v1.0.0

Published

Zero-dependency, 3kb HTTP client built on native fetch. A secure alternative to axios — no supply chain risk.

Readme


axios was just compromised via a supply chain attack (March 2026). One of its transitive dependencies was hijacked, putting millions of projects at risk.

reqcraft has zero dependencies — there's nothing to compromise.

The entire source is ~500 lines of TypeScript. You can read and audit it in 5 minutes.


Why reqcraft?

| | reqcraft | axios | |---|---|---| | Dependencies | 0 | 8+ transitive | | Bundle size | ~3kb min | ~13kb min | | Supply chain risk | None | Demonstrated (Mar 2026) | | Built on | Native fetch | XMLHttpRequest | | TypeScript | First-class, strict | Bolted on | | Tree-shakeable | Yes | No | | Full audit time | 5 minutes | Hours |

Install

npm install reqcraft
pnpm add reqcraft
yarn add reqcraft

Quick Start

import { createClient } from "reqcraft";

const api = createClient({
  baseURL: "https://api.example.com",
  headers: { Authorization: "Bearer token" },
  timeout: 5000,
});

// GET with typed response
const { data } = await api.get<User[]>("/users");

// POST — JSON auto-serialized
await api.post("/users", { name: "John", email: "[email protected]" });

// PUT
await api.put("/users/1", { name: "Jane" });

// DELETE
await api.delete("/users/1");

Default Instance

import reqcraft from "reqcraft";

const { data } = await reqcraft.get<User[]>("https://api.example.com/users");

Features

Query Parameters

api.get("/search", { params: { q: "reqcraft", page: 1, active: true } });
// GET /search?q=reqcraft&page=1&active=true

Timeout

const api = createClient({ timeout: 5000 });

// Override per request
api.get("/slow-endpoint", { timeout: 15000 });

Uses native AbortController under the hood — no polyfills.

Request Cancellation

const controller = new AbortController();

api.get("/users", { signal: controller.signal });

controller.abort();

Retry with Backoff

api.get("/flaky-endpoint", {
  retry: 3,
  retryDelay: 300, // 300ms, 600ms, 900ms (linear backoff)
});

Only retries on 5xx errors, 429 (rate limited), or network failures. Safe by default.

Interceptors

// Add auth header to every request
api.interceptors.request.use((config) => {
  config.headers = {
    ...config.headers,
    "X-Request-Id": crypto.randomUUID(),
  };
  return config;
});

// Log every response
api.interceptors.response.use((response) => {
  console.log(`${response.config.method} ${response.config.url} → ${response.status}`);
  return response;
});

// Remove an interceptor
const id = api.interceptors.request.use(fn);
api.interceptors.request.eject(id);

Error Handling

import { isReqcraftError } from "reqcraft";
import type { ReqcraftError } from "reqcraft";

try {
  await api.get("/not-found");
} catch (err) {
  if (isReqcraftError(err)) {
    console.log(err.status);     // 404
    console.log(err.statusText); // "Not Found"
    console.log(err.data);       // Response body
  }
}

Response Types

// JSON (default)
const { data } = await api.get<User>("/user/1");

// Blob
const { data: blob } = await api.get<Blob>("/file.pdf", { responseType: "blob" });

// ArrayBuffer
const { data: buffer } = await api.get<ArrayBuffer>("/binary", { responseType: "arraybuffer" });

// Text
const { data: html } = await api.get<string>("/page", { responseType: "text" });

// ReadableStream
const { data: stream } = await api.get<ReadableStream>("/stream", { responseType: "stream" });

Download Progress

api.get("/large-file", {
  responseType: "blob",
  onDownloadProgress: ({ loaded, total, percent }) => {
    console.log(`${percent}% downloaded (${loaded}/${total} bytes)`);
  },
});

Transform Request / Response

const api = createClient({
  transformRequest: [(data) => {
    // Modify data before sending
    return { ...data, timestamp: Date.now() };
  }],
  transformResponse: [(data) => {
    // Transform response data
    return data;
  }],
});

Validate Status

const api = createClient({
  validateStatus: (status) => status < 500, // treat 4xx as success
});

FormData & File Uploads

const form = new FormData();
form.append("avatar", fileInput.files[0]);
form.append("name", "John");

await api.post("/upload", form);
// FormData is auto-detected — no manual content-type needed

Plugins

import { createClient, usePlugin } from "reqcraft";
import type { ReqcraftPlugin } from "reqcraft";

const logger: ReqcraftPlugin = {
  name: "logger",
  install(instance) {
    instance.interceptors.request.use((config) => {
      console.log(`→ ${config.method} ${config.url}`);
      return config;
    });
    instance.interceptors.response.use((response) => {
      console.log(`← ${response.status}`);
      return response;
    });
  },
};

const api = createClient({ baseURL: "https://api.example.com" });
usePlugin(api, logger);

API Reference

createClient(defaults?)

Creates a new reqcraft instance with optional defaults.

HTTP Methods

| Method | Signature | |--------|-----------| | get | get<T>(url, config?) | | post | post<T>(url, body?, config?) | | put | put<T>(url, body?, config?) | | patch | patch<T>(url, body?, config?) | | delete | delete<T>(url, config?) | | head | head<T>(url, config?) | | options | options<T>(url, config?) |

Config Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | baseURL | string | — | Prepended to all request URLs | | headers | Record<string, string> | — | Default headers | | timeout | number | — | Request timeout in ms | | retry | number | 0 | Number of retry attempts | | retryDelay | number | 300 | Base delay between retries (ms) | | params | Record<string, string \| number \| boolean> | — | URL query parameters | | signal | AbortSignal | — | Abort signal for cancellation | | responseType | "json" \| "text" \| "blob" \| "arraybuffer" \| "stream" | "json" | Response body type | | validateStatus | (status: number) => boolean | s >= 200 && s < 300 | Define success status codes | | transformRequest | Function[] | — | Transform request data pipeline | | transformResponse | Function[] | — | Transform response data pipeline | | onDownloadProgress | Function | — | Download progress callback |

Utilities

| Function | Description | |----------|-------------| | isReqcraftError(error) | Type guard to check if an error is a ReqcraftError | | usePlugin(instance, plugin) | Install a plugin on a reqcraft instance |

Coming from axios?

Check out the Migration Guide — it takes about 2 minutes.

Security

Read our Security Policy to understand why zero dependencies matter.

License

MIT