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

@zap-studio/fetch

v0.5.4

Published

A type-safe fetch wrapper for HTTP requests with runtime schema validation.

Downloads

326

Readme

@zap-studio/fetch

A small fetch wrapper with Standard Schema response validation.

Installation

npm install @zap-studio/fetch

Quick Example

import { api } from "@zap-studio/fetch";
import { z } from "zod";

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

const user = await api.get("https://api.example.com/users/1", UserSchema);

console.log(user.name);

Before

Without response validation, fetch code often needs unchecked assertions:

type User = {
  id: number;
  name: string;
};

const response = await fetch("/api/users/1");
const data = await response.json();

const user = data as User;

After

With @zap-studio/fetch, the response is validated before it is returned:

import { api } from "@zap-studio/fetch";

const user = await api.get("/api/users/1", UserSchema);

The returned value is inferred from the schema.

Raw Fetch Usage

Use $fetch without a schema when you want the normal Response object.

import { $fetch } from "@zap-studio/fetch";

const response = await $fetch("/api/users/1");
const user = await response.json();

Native fetch options are passed through as RequestInit.

const response = await $fetch("/api/users", {
  method: "POST",
  body: JSON.stringify({ name: "Ada" }),
  headers: {
    "Content-Type": "application/json",
  },
});

Validated Fetch Usage

Pass a Standard Schema as the second argument to validate the JSON response.

import { $fetch } from "@zap-studio/fetch";

const user = await $fetch("/api/users/1", UserSchema);

The schema validates the response body, not the request body.

When validation fails, a ValidationError is thrown by default.

API Method Helpers

The api export provides common HTTP methods.

import { api } from "@zap-studio/fetch";

const user = await api.get("/api/users/1", UserSchema);

const created = await api.post("/api/users", UserSchema, {
  json: {
    name: "Ada",
    email: "[email protected]",
  },
});

In this example, UserSchema validates the response from /api/users. The json value is the outgoing request body and is not validated by UserSchema.

These helpers set the HTTP method for you. For raw responses without validation, use $fetch.

JSON Request Bodies

Use json when you want the package to serialize a JSON request body.

await api.post("/api/users", UserSchema, {
  json: {
    name: "Ada",
  },
});

The schema argument still validates the response. Validate request bodies separately before passing them to json if your application needs outgoing payload validation.

This sets:

  • body to JSON.stringify(json)
  • Content-Type to application/json when no content type was already provided

Use native body for standard fetch behavior.

await $fetch("/api/upload", {
  method: "POST",
  body: formData,
});

body and json are mutually exclusive.

Query Params

Use searchParams for per-request query params.

await api.get("/api/users", UserListSchema, {
  searchParams: {
    page: "1",
    limit: "20",
  },
});

searchParams accepts the same input shapes as new URLSearchParams(...).

await api.get("/api/users", UserListSchema, {
  searchParams: new URLSearchParams({ q: "ada" }),
});

await api.get("/api/users", UserListSchema, {
  searchParams: "q=ada&page=1",
});

await api.get("/api/users", UserListSchema, {
  searchParams: [["q", "ada"]],
});

When defaults, URL query params, and request params overlap, later values win:

  1. createFetch({ searchParams })
  2. query params already present in the URL
  3. per-request searchParams

Creating a Client

Use createFetch to configure shared defaults.

import { createFetch } from "@zap-studio/fetch";

const { $fetch, api } = createFetch({
  baseURL: "https://api.example.com",
  headers: {
    Authorization: `Bearer ${token}`,
  },
  searchParams: {
    locale: "en",
  },
});

const user = await api.get("/users/1", UserSchema);
const response = await $fetch("/health");

Supported defaults:

| Option | Description | | ------------------------ | ----------------------------------------------- | | baseURL | Base URL for relative request URLs | | headers | Headers applied to every request | | searchParams | Query params applied to every request | | throwOnFetchError | Throw FetchError for non-ok responses | | throwOnValidationError | Throw ValidationError for validation failures |

Non-throwing Validation

Set throwOnValidationError: false to receive the raw Standard Schema result.

const result = await api.get("/api/users/1", UserSchema, {
  throwOnValidationError: false,
});

if (result.issues) {
  console.error("Validation failed:", result.issues);
} else {
  console.log("Validation passed:", result.value);
}

HTTP Errors

By default, non-ok responses throw a FetchError.

import { FetchError } from "@zap-studio/fetch/errors";

try {
  await api.get("/api/users/404", UserSchema);
} catch (error) {
  if (error instanceof FetchError) {
    console.error(error.status);
    console.error(error.response);
  }
}

Disable this behavior when you want to handle the raw response yourself.

const response = await $fetch("/api/users/404", {
  throwOnFetchError: false,
});

console.log(response.status);

Request Objects

The first argument (input) uses the same type as global fetch.

const request = new Request("https://api.example.com/users/1", {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

const user = await $fetch(request, UserSchema);

Per-request options are merged on top of the Request.

Standard Schema

@zap-studio/fetch validates responses through @zap-studio/validation, so it works with any validation library that implements Standard Schema.

Examples include:

You can also import Standard Schema types from @zap-studio/validation when needed.

import type { StandardSchemaV1 } from "@zap-studio/validation";

Choosing the Right API

| API | Use when | | ------------- | ------------------------------------------------- | | $fetch | You want raw fetch behavior or a custom method | | api.get | You want a validated GET request | | api.post | You want a validated POST request | | api.put | You want a validated PUT request | | api.patch | You want a validated PATCH request | | api.delete | You want a validated DELETE request | | createFetch | You want shared defaults like base URL or headers |

License

MIT