typed-fetch

Overview

typed-fetch - Privacy-first, status-aware fetch with automatic TypeScript types

Welcome to typed-fetch

What is typed-fetch?

Status-aware fetch wrapper that learns response shapes and generates TypeScript types from real traffic.

typed-fetch eliminates manual type definitions for HTTP APIs while maintaining strict privacy. It observes your real network traffic, infers response shapes, and generates accurate TypeScript typesβ€”all without storing raw data.

Why typed-fetch?

⚑ Automatic Type Generation

Learn response shapes from actual API calls and generate TypeScript definitions automatically. No manual typing required.

πŸ”’ Privacy First

Only structure is recorded, never raw response values. Strict privacy mode enabled by default protects sensitive data.

πŸ“Š Status-Aware

Discriminated union types for HTTP status codesβ€”type-safe error handling without try/catch blocks.

πŸ—„οΈ Built-in Cache

In-flight deduplication, stale-while-revalidate, GC, retry with back-off, and invalidation. Zero dependencies.

βš›οΈ React Hooks

useTypedFetch, useTypedMutation, and TypedFetchProvider powered by useSyncExternalStore. Re-renders when cache updates.

πŸ”Œ Server Adapters

Hono, Next.js App Router, and a generic adapter that auto-generate types from server-side responses in development.

🌐 Works Everywhere

Seamless support for Node.js, browsers, and mixed client/server architectures with unified type generation.

πŸš€ Never Throws

Network errors return result objects, never exceptions. Explicit error handling with full type safety.

Quick Start

npm install @phumudzo/typed-fetch
pnpm add @phumudzo/typed-fetch
yarn add @phumudzo/typed-fetch

Node.js 18+ is required.

Basic Example

import { typedFetch } from '@phumudzo/typed-fetch';

const result = await typedFetch(
  'https://api.example.com/users/123',
  { method: 'GET' },
  { endpointKey: 'GET /users/:id' }
);

if (result.status === 200) {
  console.log(result.data); // ✨ fully typed after `typed-fetch generate`
}

With Caching

import { typedFetch, createTypedFetchCache } from '@phumudzo/typed-fetch';

const cache = createTypedFetchCache({ staleTime: 60_000 });

// First call β€” hits the network
const r1 = await typedFetch('/users/1', undefined, {
  endpointKey: 'GET /users/:id',
  cache,
});

// Within staleTime β€” instant, no network request
const r2 = await typedFetch('/users/1', undefined, {
  endpointKey: 'GET /users/:id',
  cache,
});

React Hooks

import { createTypedFetchCache } from '@phumudzo/typed-fetch';
import { TypedFetchProvider, useTypedFetch } from '@phumudzo/typed-fetch/react';

const cache = createTypedFetchCache({ staleTime: 60_000 });

function App() {
  return (
    <TypedFetchProvider cache={cache}>
      <UserCard id={1} />
    </TypedFetchProvider>
  );
}

function UserCard({ id }: { id: number }) {
  const { result, isLoading, isError, error } = useTypedFetch(
    `/api/users/${id}`,
    undefined,
    { endpointKey: 'GET /api/users/:id' },
  );

  if (isLoading) return <p>Loading…</p>;
  if (isError)   return <p>Error: {error?.error.message}</p>;
  if (result?.status === 200) return <h1>{result.data.name}</h1>;
  return null;
}

Server Adapters (auto type generation)

// Hono β€” one line covers all routes
import { typedFetchObserver } from '@phumudzo/typed-fetch/adapters/hono';
app.use('*', typedFetchObserver()); // dev-only, no-op in production

// Next.js App Router
import { withTypedFetchObserver } from '@phumudzo/typed-fetch/adapters/next';
export const GET = withTypedFetchObserver(
  'GET /api/users/:id',
  async (req) => Response.json(await getUser(req)),
);

Generate Types

Run your app to collect observations, then:

npx typed-fetch generate

(With server adapters, types regenerate automatically on every request in dev.)

Key Features Explained

Never Throws Exceptions

Unlike native fetch, typed-fetch returns all errors as result objects:

const result = await typedFetch(...);

// No try-catch needed!
if (result.status === 0) {
  handleNetworkError(result.error);
} else if (!result.ok) {
  handleHTTPError(result.status, result.data);
} else {
  processData(result.data);
}

Type-Safe Endpoint Keys

IDEs autocomplete all known endpoint keys from generated and user-declared types:

await typedFetch(url, undefined, {
  endpointKey: "G|",
  //            ^-- IDE suggests "GET /users/:id", "GET /posts", …
});

Status-Based Type Narrowing

const result = await typedFetch('/users/1', undefined, {
  endpointKey: 'GET /users/:id',
});

if (result.status === 200) {
  result.data.name;  // βœ… typed as string
}
if (result.status === 404) {
  result.data.error; // βœ… typed as string
}

Next Steps

On this page