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

1000fetches

v0.2.1

Published

A type-first HTTP client with compile-time path validation, schema validation, middleware, retries, and real-time streaming — powered by native fetch. Supports Zod, Valibot, ArkType, and any Standard Schema-compatible library.

Readme

1000fetches

Schema-powered Fetch 2.0 — where types meet runtime reality

npm version TypeScript License: MIT

Built for the 1000th call to be as safe as the first

The problem

You start with fetch, then add a wrapper for error handling. Then generics. Then path params. Then retries and timeouts. Soon every project grows its own version — slightly different, equally fragile.

Alternatives? Tiny helpers that stop halfway, or heavy Axios-style clients that add weight without type guarantees.


The idea

A type-first HTTP client that unifies validation, retries, streaming, and middleware on top of native fetch.


Highlights

  • 🧭 Compile-time path safety:pathParam can't slip through undefined
  • 🧩 Schema-driven validation — infer types at build time, verify data at runtime
  • Native streaming — observe, transform, or pipe data chunks as they flow
  • 🔁 Retries, timeouts, middleware — production essentials, zero config
  • 🎯 Method-based APIapi.get() with schema validation .schema() and extractor .data() for clean and concise code
  • 🧠 Designed for flow — clear API, predictable behavior, no hidden magic

Quickstart

npm install 1000fetches
import { createHttpClient } from '1000fetches'
import { z } from 'zod'

// Create client
const api = createHttpClient({
  baseUrl: 'https://api.example.com',
})

// Define schemas for types and safety
const userSchema = z.object({
  id: z.number(),
  ...
})

// Request data with schema validation
const userResponse = await api
  .get('/users/:id', {
    pathParams: { id: '123' },
  })
  .schema(userSchema)
// userResponse.data 👉 { id: number, ... }

// Clean data extraction with full type safety
const user = await api
  .get('/users/:id', {
    pathParams: { id: '123' },
  })
  .schema(userSchema)
  .data()
// user 👉 { id: number, ... }

Or use the default client for quick requests:

import http from '1000fetches'

const user = await http
  .get('/users/:id', {
    pathParams: { id: '123' },
  })
  .schema(userSchema)

➡️ Key Features

✔️ Type Safety That Actually Works

  • Compile-time path validation — TypeScript catches missing :userId parameters before runtime
  • Runtime schema validation — Schemas verify data at the network boundary
  • Schema-based type inference — Types inferred from schemas, no generics needed
  • Multi-schema supportZod, Valibot, ArkType, or any Standard Schema-compatible library

✔️ Production-Ready Quality

  • Smart retry logic — Exponential backoff with jitter for resilient requests
  • Timeout and cancellation support — Built-in AbortController integration
  • Structured error handlingHttpError, NetworkError, TimeoutError with full context
  • Request/Response middleware — Authentication, logging, transformations
  • Minimalistic footprint — Enterprise features without the bloat

✔️ Engineer-Friendly DX

  • Method-based APIapi.get(), api.post(), api.put(), api.patch(), api.delete(), and generic api.request() with full type safety
  • Schema-first validation — Chain .schema() for runtime validation and automatic type inference
  • Smart data extraction — Chain .data() for direct value access without .data property
  • Automatic response parsing — JSON/text responses parsed automatically
  • Real-time streaming — Access actual data chunks during upload/download
  • Zero dependencies — Optional peer dependencies, tree-shakable builds
  • TypeScript-first — Full type inference and IntelliSense support

1000fetches combines native fetch performance with enterprise-grade features and bulletproof type safety.

➡️ Usage Examples

Authentication

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

// Or dynamic auth
const api = createHttpClient({
  baseUrl: 'https://api.example.com',
  onRequestMiddleware: async context => {
    const token = await getToken()
    context.headers.set('Authorization', `Bearer ${token}`)
    return context
  },
})

Streaming

// Upload progress with actual data chunks
await api.post('/api/files', fileData, {
  onUploadStreaming: ({ chunk, transferredBytes, totalBytes }) => {
    const progress = Math.round((transferredBytes / totalBytes) * 100)
    console.log(`Uploading: ${progress}% - chunk size: ${chunk.length} bytes`)
  },
})

// Download progress
await api.get('/api/files/:id', {
  pathParams: { id: '123' },
  onDownloadStreaming: ({ chunk, transferredBytes, totalBytes }) => {
    const progress = Math.round((transferredBytes / totalBytes) * 100)
    console.log(`Downloading: ${progress}% - chunk size: ${chunk.length} bytes`)
  },
})

Error Handling

import {
  HttpError,
  NetworkError,
  TimeoutError,
  MiddlewareError,
  PathParameterError,
} from '1000fetches'

try {
  const user = await api
    .get('/users/:id', {
      pathParams: { id: '123' },
    })
    .schema(userSchema)
} catch (error) {
  if (error instanceof HttpError) {
    console.log(`HTTP ${error.status}: ${error.statusText}`)
  } else if (error instanceof NetworkError) {
    console.log('Network error:', error.message)
  } else if (error instanceof TimeoutError) {
    console.log('Request timed out')
  } else if (error instanceof MiddlewareError) {
    console.log('Middleware error:', error.message)
  } else if (error instanceof PathParameterError) {
    console.log('Path parameter error:', error.message)
  }
}

API Reference

createHttpClient(config?)

Creates a new HTTP client with optional configuration.

function createHttpClient(config?: HttpClientConfig): HttpClient

Configuration Options:

| Option | Type | Description | | ---------------------- | --------------------------------------------- | ------------------------------- | | baseUrl | string | Base URL for all requests | | headers | Record<string, string> | Default headers | | timeout | number | Default timeout in milliseconds | | retryOptions | RetryOptions | Default retry configuration | | onRequestMiddleware | (context: RequestContext) => RequestContext | Request middleware | | onResponseMiddleware | (response: ResponseType) => ResponseType | Response middleware | | schemaValidator | SchemaValidator | Custom schema validator |

HTTP Methods

All HTTP methods support schema validation and data extraction:

// GET
const user = await api
  .get('/users/:id', {
    pathParams: { id: '123' },
  })
  .schema(userSchema)
  .data()
// user 👉 Fully typed user object

// POST
const newUser = await api
  .post('/users', userData)
  .schema(userSchema)
  .data()
// newUser 👉 Fully typed user object

// PUT
const updatedUser = await api
  .put('/users/:id', userData, {
    pathParams: { id: '123' },
  })
  .schema(userSchema)
  .data()
// updatedUser 👉 Fully typed user object

// PATCH
const patchedUser = await api
  .patch('/users/:id', partialData, {
    pathParams: { id: '123' },
  })
  .schema(userSchema)
  .data()
// patchedUser 👉 Fully typed user object

// DELETE
await api.delete('/users/:id', {
  pathParams: { id: '123' },
})

Request Options

| Option | Type | Description | | --------------------- | ---------------------------------------------------------- | --------------------------------- | | pathParams | Record<string, string \| number> | Path parameters for URL templates | | params | Record<string, string \| number \| boolean \| undefined> | Query parameters | | headers | Record<string, string> | Request headers | | body | any | Request body | | timeout | number | Request timeout | | signal | AbortSignal | Request cancellation signal | | validateStatus | (status: number) => boolean | Custom status validation | | responseType | 'text' \| 'blob' \| 'arrayBuffer' | Response type override | | cache | RequestCache | Cache mode | | credentials | RequestCredentials | Credentials mode | | mode | RequestMode | Request mode | | redirect | RequestRedirect | Redirect mode | | retryOptions | RetryOptions | Retry configuration | | onUploadStreaming | (event: UploadStreamingEvent) => void | Upload streaming callback | | onDownloadStreaming | (event: DownloadStreamingEvent) => void | Download streaming callback |

Response Object

All requests return a ResponseType<T> object:

interface ResponseType<T> {
  data: T // Parsed response data
  status: number // HTTP status code
  statusText: string // HTTP status text
  headers: Record<string, string> // Response headers
  method: HttpMethod // HTTP method used
  url: string // Final URL
  raw: Response // Raw fetch Response
}

Schema Validation

The library provides a chainable API for schema validation:

// Without schema
const response = await api.get('/users/:id', {
  pathParams: { id: '123' },
})
// response 👉 ResponseType<unknown>

// With schema
const response = await api
  .get('/users/:id', {
    pathParams: { id: '123' },
  })
  .schema(userSchema)
// response 👉 ResponseType<User>

Data Extraction

The method-based API provides clean data extraction with full type safety:

// Extract untyped data (without schema)
const data = await api
  .get('/users/:id', {
    pathParams: { id: '123' },
  })
  .data()
// data 👉 unknown

// Direct typed data extraction (with schema)
const user = await api
  .get('/users/:id', {
    pathParams: { id: '123' },
  })
  .schema(userSchema)
  .data()
// user 👉 { id: number, ... }

// Works with all HTTP methods
const newUser = await api
  .post('/users', userData)
  .schema(userSchema)
  .data()
// newUser 👉 Fully typed user object

const updatedUser = await api
  .put('/users/:id', userData, { pathParams: { id: '123' } })
  .schema(userSchema)
  .data()
// updatedUser 👉 Fully typed user object

➡️ Feature Comparison

1000fetches vs Popular Alternatives

| Feature | 1000fetches | Axios | Better-fetch | Up-fetch | Native Fetch | | --------------------- | ----------------- | --------------- | ------------------ | ------------------ | ------------------ | | Bundle Size (gz) | ≈4.3 kB | ≈14.75 kB | ≈3.07 kB | ≈1.6 kB | 0 kB | | TypeScript | ✅ Full inference | ⚠️ Limited | ⚠️ Limited | ✅ Good | ❌ Manual | | Path Params | ✅ Compile-time | ❌ Manual | ❌ Manual | ❌ Manual | ❌ Manual | | Schema Validation | ✅ Multi-library | ❌ None | ⚠️ Limited | ✅ Multi-library | ❌ None | | Retry Logic | ✅ Built-in | ✅ Built-in | ❌ Manual | ✅ Built-in | ❌ Manual | | Error Handling | ✅ Structured | ✅ Good | ⚠️ Basic | ✅ Good | ⚠️ Verbose | | Middleware | ✅ Full support | ✅ Full support | ⚠️ Limited | ✅ Lifecycle hooks | ❌ None | | Streaming | ✅ Real chunks | ❌ None | ❌ None | ✅ Real chunks | ✅ Native | | API Design | ✅ Method-based | ✅ Method-based | ❌ Single function | ❌ Single function | ❌ Single function | | Tree Shaking | ✅ Good | ⚠️ Partial | ✅ Good | ✅ Perfect | ✅ |


📄 License

MIT License - see LICENSE for details.


Built for developers who believe type safety shouldn't be optional. Because your backend deserves skepticism.