🌐what-the-fetch

Introduction

Learn about what-the-fetch – a type-safe API client with schema validation using Standard Schema

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.

Why what-the-fetch?

Building API clients manually is error-prone and lacks type safety. what-the-fetch handles type-safe URL construction, automatic request/response validation, and full TypeScript inference automatically.

Why what-the-fetch?

Building API clients manually is tedious and error-prone. what-the-fetch solves common problems developers face when working with APIs:

Problems with Manual API Clients

  • 🔴 No type safety: Response types are unknown, leading to runtime errors
  • 🔴 Manual validation: You need to write validation code for every endpoint
  • 🔴 Schema duplication: Types and validation logic are often duplicated
  • 🔴 URL building errors: String concatenation leads to malformed URLs
  • 🔴 Poor developer experience: No autocomplete, no compile-time checks

How what-the-fetch Solves These Issues

what-the-fetch provides a complete solution for type-safe API clients:

  1. End-to-end type safety:

    • Full TypeScript inference from schema to response
    • Compile-time validation of requests
    • Autocomplete for all API endpoints and parameters
    • Type-safe path and query parameters
  2. Schema validation:

    • Built-in support for Standard Schema specification
    • Works with Zod, Valibot, ArkType, and more
    • Automatic request/response validation
    • Single source of truth for types and validation
  3. Clean API:

    • Simple, intuitive interface
    • Integrated with fast-url library for clean URL construction
    • Minimal boilerplate code
    • Focus on developer experience
  4. Production-ready:

    • Comprehensive test coverage
    • Small bundle size
    • Battle-tested in real-world applications
    • Regular updates and maintenance

Key Features

Type-Safe

Full TypeScript support with end-to-end type inference from schema definition to response data

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 for maximum flexibility

Minimal

Small bundle size with minimal dependencies - only fast-url library and Standard Schema spec

URL Building

Integrated with fast-url library for clean, safe URL construction with path and query parameters

Developer Experience

Excellent developer experience with full autocomplete and compile-time error checking

The Problem

Building API clients manually lacks type safety and validation:

// ❌ 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! Runtime errors waiting to happen

Issues with manual API clients:

  • 🔴 No type safety for request parameters
  • 🔴 Unknown response types
  • 🔴 Manual validation required
  • 🔴 Schema and types are duplicated
  • 🔴 Difficult to maintain and refactor

The Solution

what-the-fetch provides a type-safe API client with automatic validation:

// ✅ Type-safe with validation
import { createFetch } from 'what-the-fetch';
import { z } from 'zod';

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() })
  }
};

const apiFetch = createFetch(api, baseUrl);
const user = await apiFetch('/users/:id', {
  params: { id },
  query: { fields }
});
// user is fully typed as { id: number; name: string; email: string }

Benefits:

  • ✅ Full end-to-end type safety
  • ✅ Automatic request/response validation
  • ✅ Single source of truth for types and validation
  • ✅ Clean separation of concerns
  • ✅ Easy to read and maintain

Type Safety & Validation

what-the-fetch handles type-safe URL construction, automatic validation, and full TypeScript inference - catching errors at compile time instead of runtime.

When to Use what-the-fetch

what-the-fetch is ideal for:

  • 🌐 REST API clients: Building type-safe wrappers around REST APIs
  • 🔗 Microservices: Creating typed clients for service-to-service communication
  • 📱 Frontend applications: Type-safe data fetching with validation
  • 🛠️ Backend services: Calling external APIs with full type safety
  • 📊 SDK development: Building SDKs with excellent developer experience

Quick Example

Here's a real-world example showing how what-the-fetch simplifies API client development:

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

// Define API schema once (methods inferred automatically)
const api = {
  '/users/:id': {  // GET (no body)
    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': {  // POST (has body)
    body: z.object({ name: z.string(), email: z.string() }),
    response: z.object({ id: z.number(), name: z.string() })
  }
};

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

// Type-safe GET request with validation
const user = await apiFetch('/users/:id', {
  params: { id: 123 },
  query: { fields: 'name,email' }
});
// user is typed as { id: number; name: string; email: string }

// Type-safe POST request (method inferred from body)
const newUser = await apiFetch('/users', {
  body: { name: 'John', email: 'john@example.com' }
});
// Define types manually
interface User {
  id: number;
  name: string;
  email: string;
}

// Build URL manually
const url = `https://api.example.com/users/${id}?fields=${fields}`;

const response = await fetch(url);

if (!response.ok) {
  throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json();

// Manual validation required
if (typeof data.id !== 'number' || 
    typeof data.name !== 'string' || 
    typeof data.email !== 'string') {
  throw new Error('Invalid response');
}

const user: User = data;
// No compile-time guarantees that data matches User type