Socket
Book a DemoInstallSign in
Socket

what-the-fetch

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

what-the-fetch

Type-safe API client with schema validation using Standard Schema

latest
Source
npmnpm
Version
2.2.1
Version published
Maintainers
1
Created
Source

what-the-fetch! NPM Downloads JSR

Type-safe API client with schema validation using Standard Schema.

Test codecov Quality Gate Status Bundle Size CodSpeed Badge

what-the-fetch is a type-safe API client library that integrates schema validation with fetch requests, leveraging the Standard Schema specification for maximum flexibility and type safety.

Features

  • Type-safe: Full TypeScript support with end-to-end type inference
  • Schema validation: Built-in support for Standard Schema (compatible with Zod, Valibot, ArkType, and more)
  • Flexible: Works with any schema library that implements Standard Schema
  • Minimal: Small bundle size with minimal dependencies
  • URL building: Integrated with fast-url for clean URL construction

Installation

# Using npm
npm install what-the-fetch

# Using bun
bun add what-the-fetch

# Using JSR (recommended for Deno)
deno add jsr:@hckhanh/what-the-fetch

Usage

Basic Example

import { createFetch } from 'what-the-fetch';
import { z } from 'zod';

// Define your API schema
const api = {
  '/users/:id': {
    params: z.object({ id: z.number() }),
    query: z.object({ fields: z.string().optional() }),
    response: z.object({
      id: z.number(),
      name: z.string(),
      email: z.string(),
    }),
  },
  '/users': {
    query: z.object({
      limit: z.number().optional(),
      offset: z.number().optional(),
    }),
    response: z.array(z.object({
      id: z.number(),
      name: z.string(),
    })),
  },
} as const;

// Create a typed fetch function
const apiFetch = createFetch(api, 'https://api.example.com');

// Make type-safe requests
const user = await apiFetch('/users/:id', {
  params: { id: 123 },
  query: { fields: 'name,email' },
});
// user is typed as { id: number; name: string; email: string }

const users = await apiFetch('/users', {
  query: { limit: 10, offset: 0 },
});
// users is typed as Array<{ id: number; name: string }>

With POST requests

const api = {
  '/users': {
    body: z.object({
      name: z.string(),
      email: z.string().email(),
    }),
    response: z.object({
      id: z.number(),
      name: z.string(),
      email: z.string(),
    }),
  },
} as const;

const apiFetch = createFetch(api, 'https://api.example.com');

const newUser = await apiFetch('/users', {
  body: {
    name: 'John Doe',
    email: 'john@example.com',
  },
});

With HTTP Methods

what-the-fetch automatically infers HTTP methods: requests with a body use POST, and requests without a body use GET. You can also explicitly specify methods using the @method prefix for clarity or when you need other HTTP methods:

const api = {
  // Automatic method inference (these are equivalent)
  '/users/:id': {  // Uses GET (no body)
    params: z.object({ id: z.number() }),
    response: z.object({ id: z.number(), name: z.string() }),
  },
  '@get/users/:id': {  // Explicitly GET - same as above
    params: z.object({ id: z.number() }),
    response: z.object({ id: z.number(), name: z.string() }),
  },
  
  // POST is inferred when body is present
  '/users': {  // Uses POST (has body)
    body: z.object({ name: z.string(), email: z.string().email() }),
    response: z.object({ id: z.number(), name: z.string() }),
  },
  
  // Explicit methods for PUT, PATCH, DELETE
  '@put/users/:id': {
    params: z.object({ id: z.number() }),
    body: z.object({ name: z.string(), email: z.string().email() }),
    response: z.object({ id: z.number(), name: z.string() }),
  },
  '@delete/users/:id': {
    params: z.object({ id: z.number() }),
    response: z.object({ success: z.boolean() }),
  },
} as const;

const apiFetch = createFetch(api, 'https://api.example.com');

// These are equivalent - both use GET
const user1 = await apiFetch('/users/:id', { params: { id: 123 } });
const user2 = await apiFetch('@get/users/:id', { params: { id: 123 } });

// POST (inferred from body)
const newUser = await apiFetch('/users', {
  body: { name: 'John Doe', email: 'john@example.com' },
});

// Explicit methods for clarity
await apiFetch('@put/users/:id', {
  params: { id: 123 },
  body: { name: 'Jane Doe', email: 'jane@example.com' },
});
await apiFetch('@delete/users/:id', { params: { id: 123 } });

### With Shared Headers

You can provide shared headers when creating the fetch function:

```typescript
const apiFetch = createFetch(
  api,
  'https://api.example.com',
  {
    headers: {
      'Authorization': 'Bearer token',
    },
  }
);

// All requests will include the Authorization header
const user = await apiFetch('/users/:id', { params: { id: 123 } });

With Per-Request Headers

You can also provide per-request headers that will be merged with shared headers:

const apiFetch = createFetch(api, 'https://api.example.com');

const user = await apiFetch(
  '/users/:id',
  { params: { id: 123 } },
  {
    headers: {
      'Authorization': 'Bearer token',
      'X-Custom-Header': 'value',
    },
  }
);

API

createFetch(schema, baseUrl, sharedInit?)

Creates a type-safe fetch function for your API.

Parameters:

  • schema: An object mapping API paths to their schema definitions
  • baseUrl: The base URL for all API requests
  • sharedInit (optional): Shared RequestInit options that will be merged with per-request options

Returns: A typed fetch function that accepts:

  • path: The API path (must be a key from your schema)
  • options (optional): Request options (params, query, body) based on the path's schema
  • init (optional): Per-request RequestInit to customize the fetch request (merged with sharedInit)

Schema Definition

Each path in your schema can have:

  • params: Schema for URL path parameters (e.g., :id) - Required for parameterized paths
  • query: Schema for query string parameters
  • body: Schema for request body (automatically sets method to POST)
  • response: Schema for response validation

All schemas must implement the Standard Schema specification.

Note: If your path contains parameters (e.g., /users/:id), you must define a params schema. The library will throw an error at runtime if you attempt to use a parameterized path without a params schema.

Why what-the-fetch?

Building API clients manually is error-prone and lacks type safety:

// ❌ No type safety, manual validation
const response = await fetch(`${baseUrl}/users/${id}?fields=${fields}`);
const data = await response.json();
// What type is data? Who knows!
// ✅ Type-safe with validation
const user = await apiFetch('/users/:id', {
  params: { id },
  query: { fields },
});
// user is fully typed and validated!

what-the-fetch handles:

  • Type-safe URL construction with path and query parameters
  • Automatic request/response validation
  • Clean separation of concerns
  • Full TypeScript inference

Standard Schema Support

what-the-fetch works with any schema library that implements Standard Schema:

Contributing

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

License

MIT

Keywords

fetch

FAQs

Package last updated on 12 Nov 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts