πŸš€ Big News:Socket Has Acquired Secure Annex.Learn More β†’
Socket
Book a DemoSign in
Socket

fetch-smartly

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fetch-smartly

Zero-dependency, isomorphic HTTP client with intelligent retry, circuit breaker, and offline queue for Node.js and browsers

latest
Source
npmnpm
Version
1.0.2
Version published
Weekly downloads
309K
20.76%
Maintainers
1
Weekly downloads
Β 
Created
Source

version

fetch-smartly

A production-grade fetch wrapper that makes HTTP requests resilient, intelligent, and effortless.
Zero-dependency, isomorphic HTTP client with intelligent retry, circuit breaker, and offline queue for Node.js and browsers

license node typescript tests codecov downloads

The Problem

The native fetch API gives you no help when things go wrong. Servers return 503, rate limits hit 429, networks drop, timeouts expire β€” and fetch just throws a generic error. You end up writing the same retry logic, timeout management, and error classification in every project, or pulling in heavyweight libraries with dozens of dependencies.

Why fetch-smartly?

  • Zero dependencies β€” built entirely on native Web APIs, no supply chain risk
  • Intelligent retry β€” exponential backoff with jitter, Retry-After header respect, never retries 4xx client errors
  • Typed error hierarchy β€” NetworkError, TimeoutError, HttpError, RateLimitError with instanceof support
  • Circuit breaker β€” automatic failure isolation (open/half-open/closed)
  • Request deduplication β€” concurrent identical GET/HEAD requests share one fetch
  • Offline queue β€” queue failed requests for replay with pluggable storage
  • Isomorphic β€” Node.js 18+, browsers, Cloudflare Workers, Deno, Bun
  • Strict TypeScript β€” no any, full type safety, JSDoc on every export

Table of Contents

How It Works

  Request ──► Timeout Guard ──► fetch() ──► Response Parser
     β”‚              β”‚               β”‚               β”‚
     β”‚              β”‚          on failure           β”‚
     β”‚              β”‚               β–Ό               β”‚
     β”‚              β”‚       Failure Analyzer        β”‚
     β”‚              β”‚        (classify error)       β”‚
     β”‚              β”‚               β”‚               β”‚
     β”‚              β”‚          retryable?           β”‚
     β”‚              β”‚          β–Ό       β–Ό            β”‚
     β”‚              β”‚        YES      NO ──► throw  β”‚
     β”‚              β”‚         β”‚                     β”‚
     β”‚              β”‚    Backoff Manager            β”‚
     β”‚              β”‚    (delay + jitter)           β”‚
     β”‚              β”‚         β”‚                     β”‚
     β”‚              └─── retry loop β—„β”€β”€β”˜            β”‚
     β”‚                                              β”‚
     └──────────── SmartFetchResponse β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Installation

npm install fetch-smartly

Quick Start

import { fetchWithRetry } from 'fetch-smartly';

const response = await fetchWithRetry({
  url: 'https://api.example.com/data',
  method: 'GET',
  timeout: 5000,
});

console.log(response.data);    // parsed JSON or text
console.log(response.status);  // 200
console.log(response.retries); // 0
console.log(response.duration); // 142 (ms)

Configuration Options

OptionTypeDefaultDescription
urlstringrequiredThe URL to fetch
methodHttpMethod'GET'HTTP method
timeoutnumber10000Request timeout in ms
retry.maxRetriesnumber3Maximum retry attempts
retry.baseDelaynumber1000Base backoff delay in ms
retry.maxDelaynumber30000Maximum delay cap in ms
retry.backoffFactornumber2Exponential multiplier
retry.jitterbooleantrueRandomize delay
retry.retryOnnumber[][408,429,500,502,503,504]Status codes to retry
retry.retryOnNetworkErrorbooleantrueRetry on network failures
retry.shouldRetryfunctionundefinedCustom retry predicate
signalAbortSignalundefinedUser-provided abort signal
onRetryfunctionundefinedCallback before each retry
debugbooleanfalseVerbose logging

All native RequestInit options (headers, body, credentials, etc.) are also supported.

Error Handling

All errors extend SmartFetchError and support instanceof checks:

import {
  fetchWithRetry,
  NetworkError,
  TimeoutError,
  HttpError,
  RateLimitError,
} from 'fetch-smartly';

try {
  await fetchWithRetry({ url: 'https://api.example.com/data' });
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log('Rate limited. Retry after:', error.retryAfter, 'ms');
  } else if (error instanceof TimeoutError) {
    console.log('Timed out after', error.timeout, 'ms');
  } else if (error instanceof HttpError) {
    console.log('HTTP', error.status, error.body);
  } else if (error instanceof NetworkError) {
    console.log('Network failure:', error.message);
  }
}
Error ClassCodeRetryableExtra Fields
NetworkErrorERR_NETWORKYescause
TimeoutErrorERR_TIMEOUTYestimeout
HttpErrorERR_HTTPPer statusstatus, headers, body
RateLimitErrorERR_RATE_LIMITYesretryAfter
CircuitOpenErrorERR_CIRCUIT_OPENNoresetAt

Retry with Callbacks

await fetchWithRetry({
  url: 'https://api.example.com/data',
  retry: {
    maxRetries: 5,
    baseDelay: 500,
    shouldRetry: (ctx) => ctx.attempt < 3,
  },
  onRetry: (ctx) => {
    console.log(`Attempt ${ctx.attempt}/${ctx.maxRetries}, waiting ${ctx.delay}ms`);
  },
  debug: true,
});

Circuit Breaker

import { CircuitBreaker, CircuitOpenError, fetchWithRetry } from 'fetch-smartly';

const breaker = new CircuitBreaker({
  enabled: true,
  failureThreshold: 5,
  resetTimeout: 30000,
  halfOpenMaxAttempts: 1,
});

try {
  breaker.allowRequest(url, method);
  const res = await fetchWithRetry({ url });
  breaker.onSuccess();
} catch (error) {
  if (error instanceof CircuitOpenError) {
    console.log('Circuit is open, failing fast');
  } else {
    breaker.onFailure();
  }
}

Request Deduplication

import { DedupManager, getDedupKey, isDedupEligible, fetchWithRetry } from 'fetch-smartly';

const dedup = new DedupManager();

async function deduplicatedFetch(url: string) {
  const key = getDedupKey('GET', url);
  const existing = dedup.get(key);
  if (existing) return existing;
  return dedup.track(key, fetchWithRetry({ url }));
}

// Both resolve with the same response from a single fetch:
const [a, b] = await Promise.all([
  deduplicatedFetch('https://api.example.com/data'),
  deduplicatedFetch('https://api.example.com/data'),
]);

Offline Queue

import { OfflineQueue, MemoryStorage, LocalStorageBackend } from 'fetch-smartly';

const queue = new OfflineQueue(new MemoryStorage());

// Enqueue a failed request
queue.enqueue({ url: 'https://api.example.com/submit', method: 'POST', body: '{"data":1}' });

// Replay when back online
const successes = await queue.replay(async (entry) => {
  await fetch(entry.url, {
    method: entry.method,
    headers: entry.headers,
    body: entry.body,
  });
});

console.log(`Replayed ${successes}/${queue.size} requests`);

User Abort

const controller = new AbortController();
setTimeout(() => controller.abort(), 1000);

try {
  await fetchWithRetry({
    url: 'https://api.example.com/slow',
    signal: controller.signal,
  });
} catch (error) {
  // User abort β€” NOT a TimeoutError
}

Full API Reference

View full API docs

Comparison with Alternatives

Featurefetch-smartlyaxioskygot
Zero dependenciesYesNoNoNo
Native fetch basedYesNoYesNo
IsomorphicYesYesYesNode only
Edge runtime supportYesNoYesNo
Strict TypeScriptYesPartialYesYes
Retry with backoffYesPluginYesYes
Retry-After respectYesNoNoNo
Circuit breakerYesNoNoNo
Request deduplicationYesNoNoNo
Offline queueYesNoNoNo
Typed error hierarchyYesPartialPartialYes

Running Tests

npm test

86 tests across 6 test suites covering errors, request engine, retry, circuit breaker, deduplication, and offline queue.

Contributing

We welcome contributions! Please read the Contributing Guide before submitting a PR.

Look for issues labeled good first issue to get started.

Security

To report vulnerabilities, please see our Security Policy.

Support

If this package helps you, consider supporting its development:

GitHub Sponsors Buy Me a Coffee

Contributors

Contributors

License

MIT β€” Made by Ali Raza

Keywords

fetch

FAQs

Package last updated on 04 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts