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

@elghandour/fetchify

v1.2.0

Published

Universal wrapper for Fetch API

Readme

Fetchify

npm version License: MIT

A universal, lightweight wrapper for the Fetch API that works seamlessly in both browser and Node.js environments. Provides a clean, consistent interface with powerful features like interceptors, global configuration, timeout support, and more.

Features

  • 🚀 Universal: Works in browser and Node.js
  • 🔧 Interceptors: Global and per-request interceptors for advanced use cases
  • ⚙️ Flexible Configuration: Both global configs (browser) and instance-based configs (server-side) using defineConfigs and defineHeaders
  • ⏱️ Timeout Support: Built-in request timeout handling
  • 🛑 Abort Support: Easy request cancellation with AbortController
  • 📝 TypeScript: Full TypeScript support with type definitions
  • 🎯 Multiple Response Types: JSON, text, blob, arrayBuffer, formData
  • 🔄 Query Parameters: Automatic query string serialization
  • 📊 Meta Data: Pass custom metadata through requests
  • 🏗️ Vite Built: Optimized build with Vite for better performance

Installation

npm install @elghandour/fetchify

or

pnpm add @elghandour/fetchify

or

yarn add @elghandour/fetchify

Server-Side Usage

For Node.js/server environments, use instance-based configuration to avoid global state:

import { defineConfigs, defineHeaders, GET, POST } from '@elghandour/fetchify';

// Import polyfills for Node.js
import '@elghandour/fetchify/nodejs-polyfills.js';

// Create instance-based configuration
const apiConfigs = defineConfigs({
  baseURL: 'https://api.example.com'
});

const apiHeaders = defineHeaders({
  'Authorization': 'Bearer your-token',
  'Content-Type': 'application/json'
});

// Use in requests
const { data } = await GET('/users', {
  configs: apiConfigs.getAll(),
  headers: apiHeaders.getAll()
});

Quick Start

import { GET, POST } from '@elghandour/fetchify';

// Simple GET request
const { data, error } = await GET('https://api.example.com/users');
if (error) {
  console.error('Error:', error);
} else {
  console.log('Users:', data);
}

// POST request with body
const { data, error } = await POST('https://api.example.com/users', {
  body: { name: 'John Doe', email: '[email protected]' }
});

API Reference

Methods

All HTTP methods return a Promise<FetchedData<T>> with the following structure:

type FetchedData<T> = {
  data?: T;           // Response data (if successful)
  response?: Response; // Fetch Response object
  error?: any;        // Error object (if failed)
  meta: Record<string, any> | null; // Custom metadata
};

GET

GET<T>(
  route: string,
  options?: {
    params?: Record<string, any>;     // Query parameters
    configs?: Configs;                // Request configuration
    headers?: Partial<HeadersInit>;   // Request headers
    responseType?: ResponseType;      // 'json' | 'text' | 'blob' | 'arrayBuffer' | 'formData'
    meta?: Record<string, any>;       // Custom metadata
    timeout?: number;                 // Timeout in milliseconds
    requestInterceptor?: (request: FetchifyRequestParameters) => Promise<FetchifyRequestParameters>;
    responseInterceptor?: (result: FetchResult, requestInit: RequestInit, fetchParams: FetchifyRequestParameters) => Promise<FetchResult>;
  },
  abortCallback?: (controller: AbortController) => void
): Promise<FetchedData<T>>

POST

POST<T>(
  route: string,
  options?: {
    body?: any;                       // Request body
    params?: Record<string, any>;     // Query parameters
    configs?: Configs;                // Request configuration
    headers?: Partial<HeadersInit>;   // Request headers
    responseType?: ResponseType;      // Response type
    meta?: Record<string, any>;       // Custom metadata
    timeout?: number;                 // Timeout in milliseconds
    requestInterceptor?: (request: FetchifyRequestParameters) => Promise<FetchifyRequestParameters>;
    responseInterceptor?: (result: FetchResult, requestInit: RequestInit, fetchParams: FetchifyRequestParameters) => Promise<FetchResult>;
  },
  abortCallback?: (controller: AbortController) => void
): Promise<FetchedData<T>>

PUT

PUT<T>(
  route: string,
  options?: {
    body?: any;
    params?: Record<string, any>;
    configs?: Configs;
    headers?: Partial<HeadersInit>;
    responseType?: ResponseType;
    meta?: Record<string, any>;
    timeout?: number;
    requestInterceptor?: (request: FetchifyRequestParameters) => Promise<FetchifyRequestParameters>;
    responseInterceptor?: (result: FetchResult, requestInit: RequestInit, fetchParams: FetchifyRequestParameters) => Promise<FetchResult>;
  },
  abortCallback?: (controller: AbortController) => void
): Promise<FetchedData<T>>

DELETE

DELETE<T>(
  route: string,
  options?: {
    body?: any;
    params?: Record<string, any>;
    configs?: Configs;
    headers?: Partial<HeadersInit>;
    responseType?: ResponseType;
    meta?: Record<string, any>;
    timeout?: number;
    requestInterceptor?: (request: FetchifyRequestParameters) => Promise<FetchifyRequestParameters>;
    responseInterceptor?: (result: FetchResult, requestInit: RequestInit, fetchParams: FetchifyRequestParameters) => Promise<FetchResult>;
  },
  abortCallback?: (controller: AbortController) => void
): Promise<FetchedData<T>>

PATCH

PATCH<T>(
  route: string,
  options?: {
    body?: any;
    params?: Record<string, any>;
    configs?: Configs;
    headers?: Partial<HeadersInit>;
    responseType?: ResponseType;
    meta?: Record<string, any>;
    timeout?: number;
    requestInterceptor?: (request: FetchifyRequestParameters) => Promise<FetchifyRequestParameters>;
    responseInterceptor?: (result: FetchResult, requestInit: RequestInit, fetchParams: FetchifyRequestParameters) => Promise<FetchResult>;
  },
  abortCallback?: (controller: AbortController) => void
): Promise<FetchedData<T>>

HEAD

HEAD<T>(
  route: string,
  options?: {
    params?: Record<string, any>;
    configs?: Configs;
    headers?: Partial<HeadersInit>;
    meta?: Record<string, any>;
    timeout?: number;
    requestInterceptor?: (request: FetchifyRequestParameters) => Promise<FetchifyRequestParameters>;
    responseInterceptor?: (result: FetchResult, requestInit: RequestInit, fetchParams: FetchifyRequestParameters) => Promise<FetchResult>;
  },
  abortCallback?: (controller: AbortController) => void
): Promise<FetchedData<T>>

Configuration

Server-Side Configuration (Recommended)

For server-side usage, use defineConfigs and defineHeaders to create instance-based configurations that avoid global state pollution:

import { defineConfigs, defineHeaders, GET, POST } from '@elghandour/fetchify';

// Create instance-based configs and headers
const apiConfigs = defineConfigs({
  baseURL: 'https://api.example.com',
  credentials: 'include'
});

const apiHeaders = defineHeaders({
  'Authorization': 'Bearer token',
  'Content-Type': 'application/json'
});

// Use in requests
const { data } = await GET('/users', {
  configs: apiConfigs.getAll(),
  headers: apiHeaders.getAll()
});

// Update configurations for specific requests
apiHeaders.update({ 'X-API-Key': 'your-api-key' });
apiConfigs.update({ timeout: 10000 });

// Create multiple instances for different APIs
const paymentConfigs = defineConfigs({
  baseURL: 'https://payments.example.com'
});

const paymentHeaders = defineHeaders({
  'Authorization': 'Bearer payment-token'
});

Global Configuration (Browser/Client-Side)

For browser environments where global state is acceptable, you can use the global configuration:

import { globalConfigs, globalHeaders } from '@elghandour/fetchify';

// Set global configs
globalConfigs.set({
  baseURL: 'https://api.example.com',
  credentials: 'include'
});

// Update existing configs
globalConfigs.update({
  mode: 'cors'
});

// Get all configs
const configs = globalConfigs.getAll();

// Remove specific configs
globalConfigs.remove('baseURL');

// Set global headers
globalHeaders.set({
  'Authorization': 'Bearer token',
  'Content-Type': 'application/json'
});

// Update headers
globalHeaders.update({
  'X-API-Key': 'your-api-key'
});

// Get all headers
const headers = globalHeaders.getAll();

// Remove headers
globalHeaders.remove('Authorization');

Interceptors

Global Interceptors

import { setInterceptors } from '@elghandour/fetchify';

setInterceptors({
  request: async (request) => {
    // Modify request before sending
    console.log('Request:', request);
    return {
      ...request,
      headers: {
        ...request.headers,
        'X-Request-ID': Date.now().toString()
      }
    };
  },

  response: async (result, requestInit, fetchParams) => {
    // Modify response before returning
    if (result.error) {
      console.error('Response error:', result.error);
    } else {
      console.log('Response success:', result.data);
    }
    return result;
  }
});

Per-Request Interceptors

import { GET } from '@elghandour/fetchify';

// Per-request interceptors override global ones for this specific request
const { data, error } = await GET('/api/data', {
  requestInterceptor: async (request) => {
    // Custom request interceptor for this request only
    return {
      ...request,
      headers: {
        ...request.headers,
        'X-Custom-Header': 'custom-value'
      }
    };
  },
  responseInterceptor: async (result, requestInit, fetchParams) => {
    // Custom response interceptor for this request only
    if (result.error) {
      // Handle error specifically for this request
      console.log('Custom error handling');
    }
    return result;
  }
});

Request Cancellation

import { GET } from '@elghandour/fetchify';

const abortController = new AbortController();

// Method 1: Using abortCallback
const promise = GET('/api/data', {}, (controller) => {
  // Store controller for later cancellation
  abortController = controller;
});

// Cancel the request
abortController.abort();

// Method 2: Manual AbortController
const controller = new AbortController();
const promise = GET('/api/data', {
  configs: { signal: controller.signal }
});

// Cancel the request
controller.abort();

Timeout

import { GET } from '@elghandour/fetchify';

// Request with 5 second timeout
const { data, error } = await GET('/api/data', {
  timeout: 5000
});

if (error) {
  console.log('Request timed out or failed');
}

Response Types

import { GET } from '@elghandour/fetchify';

// Get JSON response (default)
const { data } = await GET('/api/users');

// Get text response
const { data: textData } = await GET('/api/text', {
  responseType: 'text'
});

// Get blob response
const { data: blobData } = await GET('/api/file', {
  responseType: 'blob'
});

// Get ArrayBuffer response
const { data: bufferData } = await GET('/api/binary', {
  responseType: 'arrayBuffer'
});

// Get FormData response
const { data: formData } = await GET('/api/form', {
  responseType: 'formData'
});

Query Parameters

Query parameters are automatically serialized:

import { GET } from '@elghandour/fetchify';

// Simple params
GET('/api/search', {
  params: { q: 'javascript', limit: 10 }
});
// Results in: /api/search?q=javascript&limit=10

// Array params
GET('/api/items', {
  params: { tags: ['react', 'typescript'] }
});
// Results in: /api/items?tags=react&tags=typescript

// Nested objects (using qs library)
GET('/api/filter', {
  params: { filter: { category: 'tech', price: { min: 100 } } }
});
// Results in: /api/filter?filter[category]=tech&filter[price][min]=100

Error Handling

import { GET } from '@elghandour/fetchify';

const { data, error, response } = await GET('/api/data');

if (error) {
  // Handle error
  if (response) {
    // HTTP error (4xx, 5xx)
    console.log('Status:', response.status);
    console.log('Error data:', error);
  } else {
    // Network error or other
    console.log('Network error:', error);
  }
} else {
  // Success
  console.log('Data:', data);
}

Advanced Usage

Creating an API Client (Server-Side)

For server-side usage, create instance-based API clients:

import { GET, POST, PUT, DELETE, defineConfigs, defineHeaders } from '@elghandour/fetchify';

// Create API configuration instance
const apiConfigs = defineConfigs({
  baseURL: 'https://jsonplaceholder.typicode.com'
});

const apiHeaders = defineHeaders({
  'Content-Type': 'application/json'
});

// Create API client with instance-based configuration
const createApiClient = (configs, headers) => ({
  getUsers: () => GET('/users', {
    configs: configs.getAll(),
    headers: headers.getAll()
  }),
  getUser: (id) => GET(`/users/${id}`, {
    configs: configs.getAll(),
    headers: headers.getAll()
  }),
  createUser: (user) => POST('/users', {
    body: user,
    configs: configs.getAll(),
    headers: headers.getAll()
  }),
  updateUser: (id, user) => PUT(`/users/${id}`, {
    body: user,
    configs: configs.getAll(),
    headers: headers.getAll()
  }),
  deleteUser: (id) => DELETE(`/users/${id}`, {
    configs: configs.getAll(),
    headers: headers.getAll()
  })
});

// Create API instance
const api = createApiClient(apiConfigs, apiHeaders);

// Usage
const users = await api.getUsers();
const user = await api.getUser(1);

Creating an API Client (Browser/Client-Side)

For browser environments, you can use global configuration:

import { GET, POST, PUT, DELETE, globalConfigs, globalHeaders } from '@elghandour/fetchify';

// Configure defaults
globalConfigs.set({
  baseURL: 'https://jsonplaceholder.typicode.com'
});

globalHeaders.set({
  'Content-Type': 'application/json'
});

// Create API methods
const api = {
  getUsers: () => GET('/users'),
  getUser: (id) => GET(`/users/${id}`),
  createUser: (user) => POST('/users', { body: user }),
  updateUser: (id, user) => PUT(`/users/${id}`, { body: user }),
  deleteUser: (id) => DELETE(`/users/${id}`)
};

export default api;

Using with React

import React, { useState, useEffect } from 'react';
import { GET } from '@elghandour/fetchify';

function UsersList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      const { data, error } = await GET('https://jsonplaceholder.typicode.com/users');

      if (error) {
        setError(error);
      } else {
        setUsers(data);
      }
      setLoading(false);
    };

    fetchUsers();
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Advanced Request Body Types

Fetchify supports various request body types automatically:

import { POST } from '@elghandour/fetchify';

// JSON object (automatically stringified)
await POST('/api/data', {
  body: { name: 'John', age: 30 }
});

// FormData
const formData = new FormData();
formData.append('file', fileInput.files[0]);
await POST('/api/upload', {
  body: formData
});

// ArrayBuffer, Blob, File, etc.
await POST('/api/binary', {
  body: new ArrayBuffer(8)
});

// URLSearchParams
const params = new URLSearchParams();
params.append('key', 'value');
await POST('/api/form', {
  body: params
});

Browser Support

Fetchify works in all modern browsers that support the Fetch API and AbortController:

  • Chrome 66+
  • Firefox 60+
  • Safari 12+
  • Edge 79+

For older browsers, consider using a polyfill.

Node.js Support

Works in Node.js with the included polyfills. The package includes Node.js polyfills for:

  • node-fetch for Fetch API
  • abort-controller for AbortController
// For Node.js usage, import the polyfills
import '@elghandour/fetchify/nodejs-polyfills.js';
import { GET } from '@elghandour/fetchify';

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Ahmed Elghandour - GitHub

Issues

If you find any bugs or have feature requests, please create an issue on GitHub.