Skip to content

junaiddshaukat/reqcraft

Repository files navigation

reqcraft

Zero-dependency HTTP client. Built on native fetch. Nothing to compromise.

npm version bundle size zero dependencies license TypeScript strict


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: "john@example.com" });

// 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

About

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

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors