typed-fetch

Browser Support

Using typed-fetch in browser and mixed client/server applications

Browser Support

typed-fetch works seamlessly in modern browsers. When running in a browser, observations are automatically sent to the local typed-fetch watch server — no extra configuration needed.

Browser Requirements

  • Modern browser with native fetch support
  • Works in all modern browsers (Chrome, Firefox, Safari, Edge)
  • Node.js 18+ for server-side usage

Basic Browser Usage

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

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

if (result.status === 200) {
  console.log(result.data);
}

No special browser setup needed — typedFetch works the same way in Node.js and the browser.

How Observations Work in the Browser

When observerMode is "auto" (the default), typed-fetch detects its runtime environment:

  • Node.js → writes observations directly to the registry file
  • Browser → POSTs observations to the local HTTP observer server at http://localhost:{observerPort}/__typed-fetch/observe

The observer server is started automatically by typed-fetch watch. From the browser's perspective, calling typedFetch() is all that's needed — observation and type generation happen in the background.

Getting Started with a Browser App

# 1. Initialize (auto-detects generatedPath from your tsconfig)
npx typed-fetch init

# 2. Start the watch server
npx typed-fetch watch
Watching .typed-fetch/registry.json
Observer listening on http://localhost:7779
  Press Ctrl+C to stop.
# 3. Run your app in another terminal
npm run dev

Make API calls in the browser. Types are regenerated automatically after each call.

Works with Any Framework

Because the observation transport is plain HTTP POST, it works with any dev setup — no plugins or framework-specific configuration required.

FrameworkSetup
Vite (React, Vue, Svelte)typed-fetch init + typed-fetch watch
Create React Apptyped-fetch init + typed-fetch watch
Next.jstyped-fetch init + typed-fetch watch
Plain HTML / no bundlertyped-fetch init + typed-fetch watch

Mixed Client/Server Applications

Both browser and Node.js calls flow through the same typed-fetch watch process:

  • Node.js calls → write directly to the registry file → watcher detects change → types regenerate
  • Browser calls → POST to observer server → observer writes to registry → watcher detects change → types regenerate

The registry merges shapes from both sources automatically, so your generated types reflect all observed responses regardless of where they originated.

Typical Workflow

┌─────────────────────────────────────────────────────────┐
│ Terminal 1: typed-fetch watch                           │
│ $ npx typed-fetch watch                                 │
│ Watching .typed-fetch/registry.json                     │
│ Observer listening on http://localhost:7779             │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ Terminal 2: Development server                          │
│ $ npm run dev                                           │
│ Browser + server both making API calls...               │
└─────────────────────────────────────────────────────────┘

             ↓ types regenerate automatically ↓

┌─────────────────────────────────────────────────────────┐
│ src/generated/typed-fetch.d.ts updated                  │
└─────────────────────────────────────────────────────────┘

React Integration Example

import { typedFetch } from '@phumudzo/typed-fetch';
import { useEffect, useState } from 'react';

export function UserProfile({ userId }: { userId: number }) {
  const [user, setUser] = useState<{ id: number; name: string; email: string } | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchUser = async () => {
      const result = await typedFetch(
        `/api/user/${userId}`,
        { method: 'GET' },
        { endpointKey: 'GET /user/:id' }
      );

      if (result.status === 200) {
        setUser(result.data); // typed once types are generated
      } else if (result.status === 404) {
        setError('User not found');
      } else {
        setError(`Error: ${result.status}`);
      }

      setLoading(false);
    };

    fetchUser();
  }, [userId]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!user) return null;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Custom Observer Port

If port 7779 conflicts with another service, change it in typed-fetch.config.json:

{
  "observerPort": 8888
}

The watch server and browser runtime both read this value, so changing it in one place is all that's needed.

CORS

The observer server runs on a different port to your dev server. typed-fetch handles this automatically — the watch server sets Access-Control-Allow-Origin: * on all responses so browser POSTs are never blocked by CORS.

Disabling Observation in Browser

To prevent observation entirely (e.g., in production):

{
  "observerMode": "none"
}

With this setting:

  • typedFetch still works normally
  • No observations are recorded or sent
  • Already-generated types continue to work

Next: API Reference

Learn about the TypeScript Types and Result Object.

On this page