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

@lonli-lokli/fetcher-zod

v0.25.0

Published

A strongly-typed fetch wrapper for TypeScript applications with runtime validation using TypeBox

Readme

@lonli-lokli/fetcher-zod

A type-safe HTTP client for TypeScript using Zod for runtime validation.

npm version License: MIT

Features

  • 🔒 Type-safe: Full end-to-end type safety from HTTP responses to your application code
  • Runtime validation: Validate API responses against Zod schemas
  • 🧩 Composable: Handle different status codes with different data shapes
  • 🔄 Transformable: Transform API responses into the shape your application needs
  • 🌐 Cross-platform: Works in Node.js, browsers, and React Native

Installation

npm install @lonli-lokli/fetcher-zod
# or
yarn add @lonli-lokli/fetcher-zod
# or
pnpm add @lonli-lokli/fetcher-zod

Note: zod is a peer dependency and must be installed separately.

Basic Usage

import { ZodFetcher, Result } from '@lonli-lokli/fetcher-zod';
import { z } from 'zod';

// Define your API response types
type ApiResponses = 
  | Result<200, { data: string[] }>
  | Result<400, { error: string }>;

// Create schemas for validation
const successSchema = z.object({ data: z.array(z.string()) });
const errorSchema = z.object({ error: z.string() });

// Create a fetcher instance
const fetcher = new ZodFetcher<ApiResponses, string[]>(
  'https://api.example.com/data',
  { 
    method: 'GET',
    headers: { 'Content-Type': 'application/json' }
  }
)
  // Handle 200 responses
  .handle(200, 
    response => response.data, // Extract the data array
    successSchema // Validate with Zod schema
  )
  // Handle 400 responses
  .handle(400, 
    response => [], // Return empty array on error
    errorSchema
  )
  // Handle any other status codes
  .discardRestAsError(response => 
    new Error(`Unexpected status code: ${response.status}`)
  );

// Execute the request
const [data, validationError] = await fetcher.run();
if (validationError) {
  console.error('Validation error:', validationError);
} else {
  console.log('Data:', data); // string[]
}

Advanced Usage

Custom Extractors

import { ZodFetcher, Result, textExtractor } from '@lonli-lokli/fetcher-zod';
import { z } from 'zod';

type ApiResponses = 
  | Result<200, string>
  | Result<404, { message: string }>;

const fetcher = new ZodFetcher<ApiResponses, string>(
  'https://api.example.com/text',
  { method: 'GET' }
)
  // Use textExtractor for plain text responses
  .handle(200, 
    text => text.toUpperCase(), 
    z.string(),
    textExtractor
  )
  // Use default JSON extractor for JSON responses
  .handle(404, 
    error => `Error: ${error.message}`,
    z.object({ message: z.string() })
  );

const [result] = await fetcher.run();

Transforming Results

import { ZodFetcher, Result } from '@lonli-lokli/fetcher-zod';
import { z } from 'zod';

type User = { id: number; name: string; email: string };
type ApiResponses = Result<200, User[]> | Result<500, { message: string }>;

const userSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email()
});

const fetcher = new ZodFetcher<ApiResponses, User[]>(
  'https://api.example.com/users',
  { method: 'GET' }
)
  .handle(200, users => users, z.array(userSchema))
  .handle(500, error => {
    console.error(error.message);
    return [];
  }, z.object({ message: z.string() }));

// Get only user names
const userNamesFetcher = fetcher.map(users => 
  users.map(user => user.name)
);

const [userNames] = await userNamesFetcher.run(); // string[]

Custom Error Handling

import { 
  ZodFetcher, 
  Result, 
  ValidationError, 
  JsonDeserializationError, 
  HandlerNotSetError,
  NetworkError,
  ParsingError
} from '@lonli-lokli/fetcher-zod';
import { z } from 'zod';

type ApiResponses = Result<200, { data: string }>;

const fetcher = new ZodFetcher<ApiResponses, string>(
  'https://api.example.com/data',
  { method: 'GET' }
)
  .handle(200, response => response.data, z.object({ data: z.string() }))
  .discardRestAsError(response => {
    if (response.status === 404) {
      return new Error('Resource not found');
    }
    if (response.status >= 500) {
      return new Error('Server error');
    }
    return new Error(`Unexpected status: ${response.status}`);
  });

try {
  const [data] = await fetcher.run();
  console.log('Success:', data);
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Schema validation failed:', error.message);
    console.error('Invalid value:', error.value);
    console.error('Expected schema:', error.schema);
    console.error('Validation details:', error.validationError.message);
  } else if (error instanceof JsonDeserializationError) {
    console.error('Failed to parse JSON response:', error.message);
    console.error('Raw response:', error.responseText);
  } else if (error instanceof HandlerNotSetError) {
    console.error(`No handler for status code ${error.code}`);
  } else if (error instanceof NetworkError) {
    console.error('Network request failed:', error.message);
    console.error('Request details:', {
      url: error.request,
      options: error.requestInit
    });
  } else if (error instanceof ParsingError) {
    console.error('Error processing response data:', error.message);
    console.error('Raw data:', error.rawData);
    console.error('Handler name:', error.handlerName);
  } else {
    console.error('Error:', error.message);
  }
}

Error Handling with Offline Detection

import { 
  ZodFetcher, 
  NetworkError 
} from '@lonli-lokli/fetcher-zod';

async function fetchWithOfflineDetection<T>(
  url: string, 
  options: RequestInit = {}
): Promise<T | null> {
  const result = await new ZodFetcher<{ code: 200; payload: T }, T>(url, options)
    .handle(200, data => data)
    .safeRun();
  
  if (result.status === 'error') {
    if (result.error instanceof NetworkError) {
      // Handle offline state
      const isOffline = !navigator.onLine;
      if (isOffline) {
        console.log('Device is offline. Please check your connection.');
        // Maybe update UI to show offline state
        // Or queue request for later when back online
      } else {
        console.error('Network request failed despite being online:', result.error.message);
      }
    }
    return null;
  }
  
  return result.data;
}

Comprehensive SafeRun Example

import { 
  ZodFetcher, 
  ValidationError, 
  JsonDeserializationError, 
  HandlerNotSetError,
  NetworkError,
  ParsingError
} from '@lonli-lokli/fetcher-zod';
import { z } from 'zod';

// Define your API response types
type ApiResponse =
  | { code: 200; payload: User }
  | { code: 400; payload: ErrorResponse }
  | { code: 404; payload: null };

// Define your data types and schemas
type User = { id: number; name: string; email: string };
type ErrorResponse = { message: string; code: string };

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});

const ErrorResponseSchema = z.object({
  message: z.string(),
  code: z.string(),
});

async function fetchUserSafely(userId: number) {
  const result = await new ZodFetcher<ApiResponse, User | string | null>(
    `/api/users/${userId}`,
    { 
      method: 'GET',
      headers: { 'Accept': 'application/json' }
    }
  )
    .handle(200, user => user, UserSchema)
    .handle(400, error => `Error: ${error.message} (${error.code})`, ErrorResponseSchema)
    .handle(404, () => null)
    // Handle any other status code with a custom error
    .discardRestAsError(response => 
      new Error(`Unexpected status code: ${response.status}`)
    )
    .safeRun();

  // Handle all possible outcomes
  if (result.status === 'ok') {
    if (result.data === null) {
      console.log('User not found');
      return null;
    } else if (typeof result.data === 'string') {
      console.log('Request error:', result.data);
      return null;
    } else {
      console.log('User found:', result.data);
      return result.data;
    }
  } else {
    // Type-specific error handling
    if (result.error instanceof ValidationError) {
      console.error('Response validation failed:', result.error.message);
      // You can access the original validation error
      const originalError = result.error.validationError;
      console.error('Validation details:', originalError.message);
    } else if (result.error instanceof JsonDeserializationError) {
      console.error('Failed to parse JSON response:', result.error.message);
    } else if (result.error instanceof HandlerNotSetError) {
      console.error(`No handler defined for status code: ${result.error.message}`);
    } else if (result.error instanceof NetworkError) {
      console.error('Network connection failed:', result.error.message);
      // Handle offline state or retry logic
    } else if (result.error instanceof ParsingError) {
      console.error('Error processing response data:', result.error.message);
      // Handle data processing errors
    } else {
      console.error('Request failed:', result.error.message);
    }
    return null;
  }
}

// Usage
const user = await fetchUserSafely(123);

API Reference

ZodFetcher<TResult, To>

The main class for creating type-safe HTTP requests.

Constructor

constructor(
  input: RequestInfo,
  init?: RequestInit,
  parser?: <T extends z.ZodType<any>>(schema: T, value: unknown) => ParsedResult<z.infer<T>>,
  fetch?: typeof fetch
)

Methods

  • handle<Code>(code, handler, schema?, extractor?): Register a handler for a specific status code
  • discardRestAsError(handler): Handle all unhandled status codes by throwing an error
  • discardRestAsTo(handler): Handle all unhandled status codes by returning a default value
  • map<B>(fn): Transform the result of this fetcher to a new type
  • run(): Execute the HTTP request and process the response

License

MIT