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

@blimu/fetch

v0.3.0

Published

Universal HTTP fetch client with hooks, retries, and streaming support for browser and Node.js

Downloads

292

Readme

@blimu/fetch

Universal HTTP fetch client with hooks, retries, and streaming support for browser and Node.js.

Features

  • 🌐 Universal: Works in both browser and Node.js environments
  • 🔄 Smart Retries: Configurable retry strategies (exponential, linear, custom)
  • 🎣 Hooks System: Extensible lifecycle hooks for request/response handling
  • 📡 Streaming Support: SSE, NDJSON, and chunked streaming parsers
  • 🚨 Type-Safe Errors: Specific error classes for different HTTP status codes
  • 🔒 Authentication: Built-in support for Bearer tokens, Basic auth, and API keys
  • Zero Dependencies: Uses native fetch API

Installation

npm install @blimu/fetch
# or
yarn add @blimu/fetch
# or
pnpm add @blimu/fetch

Quick Start

import { FetchClient } from '@blimu/fetch';

const client = new FetchClient({
  baseURL: 'https://api.example.com',
  headers: {
    'Content-Type': 'application/json',
  },
});

// Make a request
const data = await client.request({
  path: '/users',
  method: 'GET',
});

Configuration

Basic Configuration

const client = new FetchClient({
  baseURL: 'https://api.example.com',
  headers: {
    'X-Custom-Header': 'value',
  },
  timeoutMs: 5000,
  credentials: 'include',
});

Authentication

Authentication is configured using the authStrategies option:

// Bearer token authentication
const client = new FetchClient({
  baseURL: 'https://api.example.com',
  authStrategies: [
    {
      type: 'bearer',
      token: 'your-token-here',
    },
  ],
});

// Dynamic bearer token
const client = new FetchClient({
  baseURL: 'https://api.example.com',
  authStrategies: [
    {
      type: 'bearer',
      token: async () => {
        // Fetch or refresh token
        return await getToken();
      },
      headerName: 'Authorization', // Optional, defaults to "Authorization"
    },
  ],
});

// Basic authentication
const client = new FetchClient({
  baseURL: 'https://api.example.com',
  authStrategies: [
    {
      type: 'basic',
      username: 'user',
      password: 'pass',
    },
  ],
});

// API key in header
const client = new FetchClient({
  baseURL: 'https://api.example.com',
  authStrategies: [
    {
      type: 'apiKey',
      key: 'your-api-key',
      location: 'header',
      name: 'X-API-Key',
    },
  ],
});

// API key in query parameter
const client = new FetchClient({
  baseURL: 'https://api.example.com',
  authStrategies: [
    {
      type: 'apiKey',
      key: 'your-api-key',
      location: 'query',
      name: 'api_key',
    },
  ],
});

// Multiple authentication strategies
const client = new FetchClient({
  baseURL: 'https://api.example.com',
  authStrategies: [
    {
      type: 'bearer',
      token: 'token',
    },
    {
      type: 'apiKey',
      key: 'api-key',
      location: 'header',
      name: 'X-API-Key',
    },
  ],
});

// Custom authentication strategy
const client = new FetchClient({
  baseURL: 'https://api.example.com',
  authStrategies: [
    {
      type: 'custom',
      apply: async (headers, url) => {
        // Custom authentication logic
        const token = await getCustomToken();
        headers.set('X-Custom-Auth', token);
      },
    },
  ],
});

Hooks System

The hooks system allows you to intercept and modify requests at different lifecycle stages.

Available Hooks

  • beforeRequest: Before the request is made (can modify request)
  • afterRequest: After response is received, before parsing
  • afterResponse: After response is parsed
  • onError: When an error occurs
  • beforeRetry: Before a retry attempt
  • afterRetry: After a retry attempt
  • onTimeout: When a timeout occurs
  • onStreamStart: When streaming starts
  • onStreamChunk: For each stream chunk (can transform)
  • onStreamEnd: When streaming ends

Using Hooks

const client = new FetchClient({
  baseURL: 'https://api.example.com',
  hooks: {
    beforeRequest: [
      (ctx) => {
        // Add custom header
        ctx.init.headers.set('X-Request-ID', generateId());
      },
      async (ctx) => {
        // Refresh token if needed
        const token = await refreshToken();
        ctx.init.headers.set('Authorization', `Bearer ${token}`);
      },
    ],
    afterResponse: [
      (ctx) => {
        // Log response
        console.log('Response:', ctx.data);
      },
    ],
    onError: [
      (ctx) => {
        // Log errors
        console.error('Request failed:', ctx.error);
      },
    ],
  },
});

Dynamic Hook Registration

const client = new FetchClient({ baseURL: 'https://api.example.com' });

// Register a hook
client.useHook('beforeRequest', (ctx) => {
  console.log('Making request to:', ctx.url);
});

// Remove a hook
const hook = (ctx) => console.log(ctx);
client.useHook('beforeRequest', hook);
client.removeHook('beforeRequest', hook);

// Clear all hooks for a stage
client.clearHooks('beforeRequest');

// Clear all hooks
client.clearHooks();

Retry Configuration

Exponential Backoff (Default)

const client = new FetchClient({
  baseURL: 'https://api.example.com',
  retry: {
    retries: 3,
    strategy: 'exponential',
    backoffMs: 100,
    retryOn: [429, 500, 502, 503, 504],
  },
});

Linear Backoff

const client = new FetchClient({
  baseURL: 'https://api.example.com',
  retry: {
    retries: 3,
    strategy: 'linear',
    backoffMs: 200,
    retryOn: [500, 502, 503],
  },
});

Custom Retry Strategy

const client = new FetchClient({
  baseURL: 'https://api.example.com',
  retry: {
    retries: 3,
    strategy: (attempt, baseBackoff) => {
      // Custom delay calculation
      return baseBackoff * (attempt + 1) * 2;
    },
    retryOn: [500],
    retryOnError: (error) => {
      // Custom retry condition
      return error instanceof NetworkError;
    },
  },
});

Streaming

Server-Sent Events (SSE)

for await (const chunk of client.requestStream({
  path: '/events',
  method: 'GET',
  contentType: 'text/event-stream',
  streamingFormat: 'sse',
})) {
  console.log('Event:', chunk);
}

NDJSON (Newline-Delimited JSON)

for await (const item of client.requestStream({
  path: '/items',
  method: 'GET',
  contentType: 'application/x-ndjson',
  streamingFormat: 'ndjson',
})) {
  console.log('Item:', item);
}

Chunked Streaming

for await (const chunk of client.requestStream({
  path: '/stream',
  method: 'GET',
  contentType: 'application/octet-stream',
  streamingFormat: 'chunked',
})) {
  console.log('Chunk:', chunk);
}

Error Handling

The package provides specific error classes for different HTTP status codes, enabling instanceof checks in catch blocks.

Error Classes

4xx Client Errors:

  • BadRequestError (400)
  • UnauthorizedError (401)
  • ForbiddenError (403)
  • NotFoundError (404)
  • MethodNotAllowedError (405)
  • ConflictError (409)
  • UnprocessableEntityError (422)
  • TooManyRequestsError (429)
  • ClientError (generic 4xx)

5xx Server Errors:

  • InternalServerError (500)
  • BadGatewayError (502)
  • ServiceUnavailableError (503)
  • GatewayTimeoutError (504)
  • ServerError (generic 5xx)

Using Error Classes

import {
  FetchClient,
  NotFoundError,
  UnauthorizedError,
  ServerError,
} from '@blimu/fetch';

try {
  await client.request({ path: '/users/123', method: 'GET' });
} catch (error) {
  if (error instanceof NotFoundError) {
    // Handle 404
    console.log('User not found');
  } else if (error instanceof UnauthorizedError) {
    // Handle 401
    console.log('Unauthorized - refresh token');
  } else if (error instanceof ServerError) {
    // Handle any 5xx
    console.log('Server error:', error.status);
  } else {
    // Handle other errors
    console.error('Unexpected error:', error);
  }
}

Browser vs Node.js

Browser

Works out of the box in modern browsers (Chrome 42+, Firefox 39+, Safari 10.1+, Edge 14+):

import { FetchClient } from '@blimu/fetch';

const client = new FetchClient({
  baseURL: 'https://api.example.com',
});

Node.js

Node.js 22+: Native fetch is available, works out of the box.

Node.js < 22: Provide a custom fetch implementation:

import { FetchClient } from '@blimu/fetch';
import { fetch } from 'undici'; // or "node-fetch"

const client = new FetchClient({
  baseURL: 'https://api.example.com',
  fetch, // Provide custom fetch
});

API Reference

FetchClient

Constructor

new FetchClient(config?: FetchClientConfig)

Methods

  • request<T>(options: RequestOptions): Promise<T> - Make an HTTP request
  • requestStream<T>(options: StreamingRequestOptions): AsyncGenerator<T> - Make a streaming request
  • addAuthStrategy(strategy: AuthStrategy): void - Add an authentication strategy
  • clearAuthStrategies(): void - Remove all authentication strategies
  • useHook(stage: string, hook: Hook): void - Register a hook
  • removeHook(stage: string, hook: Hook): boolean - Remove a hook
  • clearHooks(stage?: string): void - Clear hooks

Types

interface FetchClientConfig {
  baseURL?: string;
  headers?: Record<string, string>;
  timeoutMs?: number;
  retry?: RetryConfig;
  hooks?: HooksConfig;
  authStrategies?: AuthStrategy[];
  fetch?: typeof fetch;
  credentials?: RequestCredentials;
}

type AuthStrategy =
  | BearerAuthStrategy
  | BasicAuthStrategy
  | ApiKeyAuthStrategy
  | CustomAuthStrategy;

interface RequestOptions extends RequestInit {
  path: string;
  method: string;
  query?: Record<string, any>;
}

interface StreamingRequestOptions extends RequestOptions {
  contentType: string;
  streamingFormat?: 'sse' | 'ndjson' | 'chunked';
}

License

MIT