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

@vielzeug/fetchit

v1.1.7

Published

**Fetchit** is a modern, type-safe HTTP client with intelligent caching and query management for TypeScript. Build data-driven applications with TanStack Query-inspired features in a lightweight package.

Readme

@vielzeug/fetchit

What is Fetchit?

Fetchit is a modern, type-safe HTTP client with intelligent caching and query management for TypeScript. Build data-driven applications with TanStack Query-inspired features in a lightweight package.

The Problem

Working with HTTP requests and caching is repetitive and error-prone:

  • Native fetch lacks type safety and requires manual error handling
  • Axios is heavy (~13KB) and doesn't include caching
  • TanStack Query is excellent but adds 15KB+ to your bundle
  • Manual cache management leads to stale data and race conditions
  • No built-in request deduplication causes redundant network calls

The Solution

Fetchit provides a clean, lightweight HTTP client with built-in query management:

import { createHttpClient, createQueryClient } from '@vielzeug/fetchit';

// HTTP client for simple requests
const http = createHttpClient({
  baseUrl: 'https://api.example.com',
  headers: { Authorization: 'Bearer token' },
});

// Query client for caching and state management
const queryClient = createQueryClient({
  staleTime: 5000,
  gcTime: 300000,
});

// Fetch with automatic caching
const user = await queryClient.fetch({
  queryKey: ['users', userId],
  queryFn: () => http.get(`/users/${userId}`),
  staleTime: 10000,
});

// Same request reuses cache – no network call!
const cachedUser = await queryClient.fetch({
  queryKey: ['users', userId],
  queryFn: () => http.get(`/users/${userId}`),
});

✨ Features

  • Type-Safe – Full TypeScript support with automatic type inference
  • Zero Dependencies – Only requires @vielzeug/toolkit for retry logic
  • Lightweight – 3.37 KB gzipped
  • Smart Caching – TanStack Query-inspired caching with stale-while-revalidate
  • Request Deduplication – Prevents duplicate in-flight requests
  • Async Validation – Built-in retry logic with exponential backoff
  • Abort Support – Cancel requests with AbortController
  • Framework Agnostic – Works anywhere JavaScript runs
  • Stable Keys – Property order doesn't matter for cache matching

🆚 Comparison with Alternatives

| Feature | Fetchit | TanStack Query | Axios | Native Fetch | | -------------------- | ------------- | -------------- | -------- | ------------ | | Bundle Size (gzip) | ~3.4 KB | ~15KB | ~13KB | 0KB | | TypeScript Support | ✅ First-class| ✅ Excellent | ✅ Good | ⚠️ Basic | | Caching | ✅ Built-in | ✅ Advanced | ❌ | ❌ | | Request Dedup | ✅ Automatic | ✅ Yes | ❌ | ❌ | | Query Management | ✅ Yes | ✅ Advanced | ❌ | ❌ | | Retry Logic | ✅ Built-in | ✅ Built-in | ⚠️ Plugin| ❌ | | Dependencies | 1 | 0 | 0 | 0 | | Framework Agnostic | ✅ Yes | ⚠️ React-first | ✅ Yes | ✅ Yes |

📦 Installation

# pnpm
pnpm add @vielzeug/fetchit
# npm
npm install @vielzeug/fetchit
# yarn
yarn add @vielzeug/fetchit

🚀 Quick Start

Simple HTTP Client

import { createHttpClient } from '@vielzeug/fetchit';

const http = createHttpClient({
  baseUrl: 'https://api.example.com',
  timeout: 5000,
  headers: { Authorization: 'Bearer token' },
});

// Make requests
const user = await http.get('/users/1');
const created = await http.post('/users', {
  body: { name: 'Alice', email: '[email protected]' },
});

// Update headers dynamically
http.setHeaders({ Authorization: 'Bearer new-token' });

Query Client with Caching

import { createQueryClient, createHttpClient } from '@vielzeug/fetchit';

const http = createHttpClient({ baseUrl: 'https://api.example.com' });
const queryClient = createQueryClient({
  staleTime: 5000, // 5 seconds
  gcTime: 300000, // 5 minutes
});

// Fetch with caching
const user = await queryClient.fetch({
  queryKey: ['users', 1],
  queryFn: () => http.get('/users/1'),
  staleTime: 5000,
  retry: 3,
});

// Same request reuses cache
const sameUser = await queryClient.fetch({
  queryKey: ['users', 1],
  queryFn: () => http.get('/users/1'),
}); // ✅ Returns cached data instantly

Using Both Together

import { createHttpClient, createQueryClient } from '@vielzeug/fetchit';

// Create HTTP client for requests
const http = createHttpClient({
  baseUrl: 'https://api.example.com',
  headers: { Authorization: 'Bearer token' },
});

// Create query client for caching
const queryClient = createQueryClient({
  staleTime: 5000,
});

// Use HTTP client for simple requests
await http.post('/analytics', { body: { event: 'click' } });

// Use query client for cached data fetching
await queryClient.fetch({
  queryKey: ['users'],
  queryFn: () => http.get('/users'),
});

// Mutations with cache invalidation
await queryClient.mutate(
  {
    mutationFn: (data) => http.post('/users', { body: data }),
    onSuccess: () => queryClient.invalidate(['users']),
  },
  { name: 'Charlie' },
);

API Reference

HTTP Client

createHttpClient(options)

Creates a simple HTTP client for making requests without caching overhead.

Options:

  • baseUrl?: string – Base URL for all requests
  • headers?: Record<string, string> – Default headers
  • timeout?: number – Request timeout in milliseconds (default: 30000)
  • dedupe?: boolean – Enable request deduplication (default: true)
  • logger?: (level, msg, meta) => void – Custom logger function

Methods:

  • get(url, config?) – GET request
  • post(url, config?) – POST request
  • put(url, config?) – PUT request
  • patch(url, config?) – PATCH request
  • delete(url, config?) – DELETE request
  • request(method, url, config?) – Custom method
  • setHeaders(headers) – Update default headers

Example:

const http = createHttpClient({
  baseUrl: 'https://api.example.com',
  timeout: 5000,
  headers: { Authorization: 'Bearer token' },
});

// GET request with query parameters
const users = await http.get<User[]>('/users', {
  query: { page: 1, limit: 10 },
});

// GET request with path parameters
const user = await http.get<User>('/users/:id', {
  params: { id: '123' },
});

// Combined path and query parameters
const posts = await http.get<Post[]>('/users/:userId/posts', {
  params: { userId: '123' },
  query: { status: 'published', limit: 10 },
});

// POST with body
const created = await http.post<User>('/users', {
  body: { name: 'Alice', email: '[email protected]' },
});

// Custom headers per request
await http.get('/protected', {
  headers: { 'X-Custom-Header': 'value' },
});

Query Client

createQueryClient(options)

Creates a query client with intelligent caching and state management.

Options:

  • staleTime?: number – Time in ms before data is considered stale (default: 0)
  • gcTime?: number – Time in ms before unused cache is garbage collected (default: 300000)

Methods:

  • fetch(options) – Fetch data with caching
  • prefetch(options) – Prefetch data (swallows errors)
  • mutate(options, variables) – Execute mutations
  • invalidate(queryKey) – Invalidate cached queries
  • setData(queryKey, data) – Manually set cache data
  • getData(queryKey) – Get cached data
  • getState(queryKey) – Get query state
  • subscribe(queryKey, listener) – Subscribe to query changes (returns unsubscribe function)
  • clear() – Clear all cached data

Example:

const queryClient = createQueryClient({
  staleTime: 5000,
  gcTime: 300000,
});

// Fetch with caching
const user = await queryClient.fetch({
  queryKey: ['users', 1],
  queryFn: () => fetch('/users/1').then((r) => r.json()),
  staleTime: 5000,
  retry: 3,
  onSuccess: (data) => console.log('Loaded:', data),
  onError: (err) => console.error('Failed:', err),
});

// Subscribe to changes
const unsubscribe = queryClient.subscribe(['users', 1], (state) => {
  console.log('State:', state.status, state.data);
});

// Manually update cache
queryClient.setData(['users', 1], (old) => ({
  ...old,
  name: 'Updated Name',
}));

// Invalidate cache
queryClient.invalidate(['users']); // Invalidates all user queries

Advanced Features

Request Deduplication

Automatically prevents duplicate in-flight requests.

const http = createHttpClient({ dedupe: true });

// These run concurrently but only make ONE request
const [user1, user2, user3] = await Promise.all([http.get('/users/1'), http.get('/users/1'), http.get('/users/1')]);

// All three get the same response
console.log(user1 === user2 && user2 === user3); // true

Retry Logic

Built-in retry with exponential backoff.

await queryClient.fetch({
  queryKey: ['users'],
  queryFn: () => fetchUsers(),
  retry: 3, // Retry 3 times (4 attempts total)
  retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),
});

Abort Requests

Cancel requests with AbortController.

const controller = new AbortController();

const promise = http.get('/slow-endpoint', {
  signal: controller.signal,
});

// Cancel after 1 second
setTimeout(() => controller.abort(), 1000);

try {
  await promise;
} catch (err) {
  console.log('Request aborted');
}

Cache Invalidation

Smart cache invalidation with prefix matching.

// Cache some data
await queryClient.fetch({
  queryKey: ['users', 1],
  queryFn: () => fetchUser(1),
});

await queryClient.fetch({
  queryKey: ['users', 2],
  queryFn: () => fetchUser(2),
});

// Invalidate all user queries
queryClient.invalidate(['users']);

// Or invalidate specific user
queryClient.invalidate(['users', 1]);

Stable Query Keys

Property order doesn't matter for cache matching.

// These are treated as the same query
const key1 = ['users', { page: 1, filter: 'active' }];
const key2 = ['users', { filter: 'active', page: 1 }];

// Both use the same cache entry
await queryClient.fetch({ queryKey: key1, queryFn: fetchUsers });
await queryClient.fetch({ queryKey: key2, queryFn: fetchUsers }); // Uses cache

Mutations

Execute mutations with optimistic updates and cache invalidation.

await queryClient.mutate(
  {
    mutationFn: async (data) => {
      return await http.post('/users', { body: data });
    },
    onSuccess: (newUser, variables) => {
      // Update cache optimistically
      queryClient.setData(['users'], (old = []) => [...old, newUser]);
    },
    onError: (error, variables) => {
      console.error('Mutation failed:', error);
    },
    onSettled: (data, error, variables) => {
      // Refetch to ensure consistency
      queryClient.invalidate(['users']);
    },
  },
  { name: 'Alice', email: '[email protected]' },
);

Subscriptions

Subscribe to query state changes.

const unsubscribe = queryClient.subscribe(['users', 1], (state) => {
  console.log('Status:', state.status);
  console.log('Data:', state.data);
  console.log('Error:', state.error);
  console.log('Loading:', state.isLoading);
  console.log('Success:', state.isSuccess);
});

// Later, unsubscribe
unsubscribe();

TypeScript Support

Full TypeScript support with automatic type inference.

import { createHttpClient, type Infer } from '@vielzeug/fetchit';

interface User {
  id: number;
  name: string;
  email: string;
}

const http = createHttpClient({ baseUrl: 'https://api.example.com' });

// Type inference
const user = await http.get<User>('/users/1');
console.log(user.name); // ✅ Type-safe

// Mutation types
await queryClient.mutate<User, { name: string; email: string }>(
  {
    mutationFn: async (vars) => {
      return await http.post<User>('/users', { body: vars });
    },
    onSuccess: (data) => {
      console.log(data.id); // ✅ Type-safe
    },
  },
  { name: 'Alice', email: '[email protected]' },
);

Error Handling

Custom error class with detailed information.

import { HttpError } from '@vielzeug/fetchit';

try {
  await http.get('/not-found');
} catch (err) {
  if (err instanceof HttpError) {
    console.log('URL:', err.url); // '/not-found'
    console.log('Method:', err.method); // 'GET'
    console.log('Status:', err.status); // 404
    console.log('Original:', err.original); // Original error
  }
}

Best Practices

Use HTTP Client for Simple Requests

When you don't need caching, use the HTTP client:

const http = createHttpClient({ baseUrl: 'https://api.example.com' });

// Simple one-off requests
await http.post('/analytics/event', { body: { event: 'click' } });

Use Query Client for Data Fetching

When you need caching and state management:

const queryClient = createQueryClient({ staleTime: 5000 });
const http = createHttpClient({ baseUrl: 'https://api.example.com' });

// Fetch and cache user data
await queryClient.fetch({
  queryKey: ['users', userId],
  queryFn: () => http.get(`/users/${userId}`),
});

Combine Both for Full-Featured Apps

Use HTTP client and query client together:

const http = createHttpClient({
  baseUrl: 'https://api.example.com',
  headers: { Authorization: 'Bearer token' },
});

const queryClient = createQueryClient({
  staleTime: 5000,
});

// HTTP client for simple requests
await http.post('/events', { body: event });

// Query client for cached data
await queryClient.fetch({
  queryKey: ['users'],
  queryFn: () => http.get('/users'),
});

// Mutations with cache invalidation
await queryClient.mutate(
  {
    mutationFn: (data) => http.post('/users', { body: data }),
    onSuccess: () => queryClient.invalidate(['users']),
  },
  userData,
);

Optimize Cache Settings

const queryClient = createQueryClient({
  staleTime: 5000, // 5 seconds – how long data is fresh
  gcTime: 300000, // 5 minutes – how long to keep unused data
});

📖 Documentation

📄 License

MIT © Helmuth Saatkamp

🤝 Contributing

Contributions are welcome! Check our GitHub repository.

🔗 Links


Part of the Vielzeug ecosystem – A collection of type-safe utilities for modern web development.

Credits

Inspired by TanStack Query and SWR.