Skip to content

anwararcoder/XFetch

Repository files navigation

πŸš€ XFetch

Universal HTTP fetching library β€” framework-agnostic, SSR-safe, TypeScript-first, zero runtime dependencies.

Created and maintained by Anwar Ramadan (AR-Coder Company)

npm version downloads Build Status TypeScript License: MIT

Built entirely on native fetch. Inspired by Axios but designed for the modern web. From a classic <script> tag or jQuery application to a Next.js Server Components setup.

Explore detailed guides, interactive examples, and full API references on our official docs site.


🌟 Features

  • πŸš€ Zero dependencies β€” Only uses modern, native fetch. Compatible with Node 18+, browsers, Edge Runtime, Bun, and Deno.
  • πŸ” Interceptors β€” Axios-style request, response, and error middleware chains.
  • ⚑ Smart Retry β€” Built-in exponential backoff with jitter and customizable status code handling.
  • πŸ—„οΈ Advanced Caching β€” Memory and LocalStorage backends, TTL-based eviction, and automatic request deduplication (no redundant network calls).
  • πŸ” Auth Management β€” Effortless Bearer token injection with a fully integrated 401 token refresh flow.
  • ❌ Cancellation β€” Seamless AbortController-based request timeouts and manual cancellation without leaks.
  • πŸ”Œ Plugin System β€” Highly extensible architecture. Extend the client safely without modifying the core.
  • βš›οΈ React / Next.js Ready β€” Includes useRequest() and useMutation() hooks.
  • 🟒 Vue / Nuxt Ready β€” Includes useApi() and useApiMutation() composables.
  • πŸ“¦ UMD Build β€” Drop-in global via CDN, fully compatible with legacy stacks like jQuery.
  • 🌐 Isomorphic & SSR-safe β€” Runs perfectly during SSR without browser-specific polyfills or memory leaks.

πŸ“¦ Installation

npm install @ar-coder/xfetch
# or
pnpm add @ar-coder/xfetch
# or
yarn add @ar-coder/xfetch

Using via CDN (UMD browser build):

<script src="https://cdn.jsdelivr.net/npm/@ar-coder/xfetch/dist/xfetch.umd.js"></script>

πŸš€ Quick Start

Creating an instance allows you to encapsulate base URLs, default headers, and global configurations such as cache and retry strategies.

import { createClient } from "@ar-coder/xfetch";

const api = createClient({
  baseURL: "https://api.example.com",
  headers: {
    Accept: "application/json",
  },
});

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

// POST β€” objects are automatically serialized to JSON
await api.post("/users", { name: "Anwar", role: "admin" });

// Seamless REST support
await api.put("/users/1", { name: "Updated" });
await api.patch("/users/1", { active: false });
await api.delete("/users/1");

πŸ“– Framework Usage

React & Next.js

XFetch exposes custom hooks directly from xfetch/react. These hooks are strictly typed and handle loading/error states out of the box.

import { createClient } from "@ar-coder/xfetch";
import { useRequest, useMutation } from "@ar-coder/xfetch/react";

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

function UserList() {
  const { data, loading, error, execute } = useRequest<User[]>(api, "/users");

  if (loading) return <div>Loading...</div>;
  if (error)
    return <div onClick={execute}>Error: {error.message} - Retry?</div>;

  return (
    <ul>
      {data?.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

function CreateUser() {
  const { mutate, loading } = useMutation<User, CreateUserInput>(
    api,
    "post",
    "/users",
  );

  return (
    <button onClick={() => mutate({ name: "Anwar", email: "me@example.com" })}>
      {loading ? "Creating..." : "Create User"}
    </button>
  );
}

Vue 3 & Nuxt

XFetch exposes composables from xfetch/vue. They automatically integrate with Vue's reactivity system.

<script setup lang="ts">
import { ref } from "vue";
import { createClient } from "@ar-coder/xfetch";
import { useApi, useApiMutation } from "@ar-coder/xfetch/vue";

const api = createClient({ baseURL: "/api" });

// Standard reactive data fetching
const { data, loading, error, execute } = useApi<User[]>(api, "/users");

// Watch reactive properties and auto-refetch
const page = ref(1);
const { data: pagedData } = useApi<User[]>(api, "/users", {
  watchSources: [page],
  params: { page: page.value },
});

// Mutations
const { mutate: createUser } = useApiMutation<User, CreateUserInput>(
  api,
  "post",
  "/users",
);
</script>

Server-Side Rendering (SSR)

XFetch distinguishes itself by seamlessly executing on the server without memory leaks or missing global object errors (window is not defined).

Next.js (App Router / Server Components):

import { createClient } from "@ar-coder/xfetch";
const api = createClient({ baseURL: "https://api.example.com" });

export default async function Page() {
  // `cache` and other node-specific deduplication methods work inherently
  const { data: users } = await api.get<User[]>("/users");
  return <UserList users={users} />;
}

Nuxt 3 UseAsyncData:

const api = createClient({ baseURL: "/api" });

// useAsyncData ensures the fetch isn't duplicated on the client-side swap
const { data } = await useAsyncData("users", () =>
  api.get<User[]>("/users").then((res) => res.data),
);

Vanilla JS & jQuery

XFetch is exported over XFetch global object when imported through a <script> tag. It works flawlessly in older environments like jQuery projects!

<script src="https://cdn.jsdelivr.net/npm/@ar-coder/xfetch/dist/xfetch.umd.js"></script>
<script>
  const api = XFetch.createClient({ baseURL: "https://api.example.com" });

  // Use as replacement for $.ajax
  $("#load-btn").on("click", async function () {
    try {
      const { data } = await api.get("/users");
      $("#list").html(data.map((u) => `<li>${u.name}</li>`).join(""));
    } catch (err) {
      console.error("Request failed: ", err.status);
    }
  });
</script>

πŸ›  Advanced Usage

Interceptors

Like Axios, you can intercept requests or responses before they are handled by then or catch.

// 1. Add headers before every request
api.interceptors.request.use((ctx) => {
  ctx.headers["X-Request-ID"] = crypto.randomUUID();
  return ctx;
});

// 2. Transform the response or track analytical metrics
api.interceptors.response.use((res) => {
  console.log(
    `[${res.status}] ${res.request.url} matched cache:`,
    res.fromCache,
  );
  return res;
});

// 3. Centralized error handling
api.interceptors.error.use((err) => {
  if (err.status === 403) window.location.href = "/login";
  return err; // re-throw so the local catch block still works
});

Caching

Stop waiting on redundant data using robust integrated caching. Two modes exist: memory (default) and localStorage (persists cross-tab).

const api = createClient({
  baseURL: "https://api.example.com",
  // Setup global caching
  cache: {
    storage: "memory",
    ttl: 5 * 60 * 1000, // Cache lives for 5 minutes
  },
});

// Force fetching logic per-request:
await api.get("/always-fresh", { cache: false });
await api.get("/use-local-storage", {
  cache: { storage: "localStorage", ttl: 3600000 },
});

Retry Strategy

Flaky network connection? Setup exponential backoff retries explicitly.

const api = createClient({
  baseURL: "https://api.example.com",
  retry: {
    count: 3, // Try 3 total times (1 initial + 3 retries = 4 max requests)
    delay: 500, // Exponentially wait: 500ms -> 1000ms -> 2000ms
    maxDelay: 5000,
    statusCodes: [408, 429, 500, 502, 503, 504], // Only retry on safe errors
  },
});

// Or disable for an explicit request:
await api.post("/transaction/process", { amount: 50 }, { retry: false });

Authorization Management

Instead of injecting your tokens via interceptor manually every time, use the powerful built-in auth manager with a native refresh flow implementation.

const api = createClient({
  baseURL: "https://api.example.com",
  auth: {
    token: null, // Initially unauthenticated
    // Intercepts 401 unauthorized errors, pauses queue, refreshes, resolves, and plays retry
    refreshToken: async () => {
      const res = await fetch("/api/auth/refresh", { method: "POST" });
      const json = await res.json();
      return json.token; // Pass new token
    },
  },
});

// Set state immediately when login is complete:
api.setAuth("my_new_oauth_token_123");

// Cleans queue + interceptor on logout:
api.clearAuth();

πŸ” Security Features

XFetch ships with production-grade security hardening out of the box. Every feature is opt-out rather than opt-in β€” you're safe by default.

🚫 SSRF & Protocol Validation

Every URL is validated against a blocklist of dangerous protocols before any network call is made. This prevents Server-Side Request Forgery and local file reads.

// These will all throw immediately β€” no network call is ever made:
api.get("file:///etc/passwd");          // ❌ Blocked: file:
api.get("javascript:alert(1)");         // ❌ Blocked: javascript:
api.get("data:text/html,<script>...");  // ❌ Blocked: data:
api.get("gopher://internal-service");   // ❌ Blocked: gopher:

// Use validateURL directly in your own code:
import { validateURL } from "@ar-coder/xfetch";
validateURL(userProvidedURL); // throws if unsafe

🧼 Credential Scrubbing in Dev Logs

When debug mode is active, XFetch automatically redacts sensitive headers and embedded URL credentials from all log output. Your tokens will never appear in the browser console or terminal.

// What you see in the console (dev mode):
[XFetch] β†’ GET https://***:***@api.example.com/users
Headers: { authorization: '[REDACTED]', cookie: '[REDACTED]', accept: 'application/json' }

πŸ›‘οΈ Prototype Pollution Protection

mergeHeaders uses a null-prototype result object and explicitly blocks dangerous keys:

// These keys are silently ignored β€” they cannot pollute Object.prototype:
mergeHeaders({ "__proto__": "...", "constructor": "..." }); // safe βœ“

πŸ”’ Auth-Aware Caching

Requests that carry Authorization, Cookie, or X-Auth-Token headers are never cached β€” regardless of your cache configuration. This prevents cross-user data leakage in SSR environments or shared memory on the server.

// Even with global cache enabled, authenticated requests always hit the network:
const api = createClient({
  baseURL: "https://api.example.com",
  cache: { storage: "memory", ttl: 60_000 },
  auth: { token: "my-token" },
});

// This request will NOT be cached β€” auth header is present:
await api.get("/profile");

πŸ” Idempotent Retry (No Double Charges)

By default, retries only happen for safe, idempotent HTTP methods (GET, HEAD, OPTIONS, PUT, DELETE). Non-idempotent methods (POST, PATCH) are never retried without explicit opt-in, preventing duplicate writes or double charges.

const api = createClient({
  retry: {
    count: 3,
    statusCodes: [408, 500, 502, 503, 504],
    // ⚠️ Explicitly opt-in to POST retries (user assumes responsibility):
    allowedMethods: ["GET", "HEAD", "OPTIONS", "POST"],
  },
});

// POST is retried ONLY when allowedMethods includes 'POST'.
// By default, this throws immediately on the first failure β€” no retries.
await api.post("/payment/charge", { amount: 99.99 });

An absolute hard cap of 10 retry attempts is enforced regardless of configuration.

⏱️ Rate Limiting

Protect your backend APIs from accidental request floods using the built-in RateLimiter. Ideal for preventing React render-loop or reactive-watcher bugs from hammering your API.

import { createClient, RateLimiter } from "@ar-coder/xfetch";

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

// Allow max 30 requests every 10 seconds
const limiter = new RateLimiter({ maxRequests: 30, windowMs: 10_000 });

api.interceptors.request.use((ctx) => {
  limiter.check(); // throws XFetchError { status: 429 } if limit exceeded
  return ctx;
});

// Check remaining budget:
console.log(limiter.remaining); // e.g. 27

🧊 Config Immutability

The client configuration object is frozen with Object.freeze() after initialization. This prevents any runtime mutation that could silently change security-critical settings like baseURL or auth.token.

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

// This silently fails (strict mode throws TypeError):
config.baseURL = "https://evil.attacker.com"; // ❌ frozen β€” no effect

We welcome community contributions constraints via Pull Requests. Please see our CONTRIBUTING.md guidelines for information.

  1. Clone the repo: git clone https://github.com/anwararcoder/XFetch.git
  2. Install dependencies: npm install
  3. Make changes in a new branch: git checkout -b fix-auth
  4. Run validation scripts:
    npm run lint        # Code styling correctness
    npm run typecheck   # TS compiler validation
    npm run test        # Execute 170+ Vitest specifications
  5. Submit a pull request!

πŸ‘¨β€πŸ’» About the Author

Anwar Ramadan is a Senior Software Engineer passionate about open-source and modern web architectures. This project is maintained under the umbrella of AR-Coder Company, dedicated to building precise, production-grade developer tooling.


πŸ“ License

Released under the MIT License. Copyright Β© 2026 Anwar Ramadan - AR-Coder Company.

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors