Skip to content
Built with

Typed Fetch ​

Build & Deploy

Import fetch from void/client for route autocomplete, checked request bodies and inferred response types.

On this pageCall your routes with full types · Pass query parameters · Handle errors

Basic usage ​

This example calls a route with the typed client.

ts
import { fetch } from 'void/client';

// GET: return type inferred from your handler
const users = await fetch('/api/users');

// POST: body type-checked against your handler's validator
await fetch('/api/users', {
  method: 'POST',
  body: { name: 'Alice', email: 'alice@example.com' },
});

// Dynamic params interpolated from the route pattern
const user = await fetch('/api/users/:id', {
  params: { id: '42' },
});

What gets type-checked ​

The client checks every part of the request:

  • Paths: only routes defined in routes/ are accepted. Typos fail at compile time.
  • Methods: only methods the route file exports are allowed. If it exports only GET, method: "POST" is an error.
  • Body, query, params: when your handler uses defineHandler.withValidator(), the validator schemas become the input types. The client enforces them at the call site.
  • Response type: the return type matches what your handler returns after JSON serialization.

Query parameters ​

This step sends search parameters on a GET request with query.

ts
// routes/api/search.ts
export const GET = defineHandler.withValidator({
  query: z.object({ q: z.string(), page: z.string().optional() }),
})((c, { query }) => {
  return { results: [], term: query.q };
});

// Client: query is type-checked
const data = await fetch('/api/search', {
  query: { q: 'hello' },
});
// data: { results: never[]; term: string }

Error handling ​

This step handles errors. Non-2xx responses throw a FetchError.

ts
import { fetch, FetchError } from 'void/client';

try {
  await fetch('/api/users/:id', { params: { id: '999' } });
} catch (e) {
  if (e instanceof FetchError) {
    console.log(e.status); // 404
    console.log(e.data); // parsed response body
    console.log(e.response); // raw Response
  }
}

Options ​

The client accepts these options.

OptionTypeDescription
methodstringHTTP method. Defaults to "GET".
bodyobjectRequest body (auto-serialized as JSON).
queryRecord<string, string>Query string parameters.
paramsRecord<string, string>URL path parameters (:id segments).
headersHeadersInitAdditional request headers.
signalAbortSignalAbort signal.
baseURLstringBase URL prepended to the path. Useful for external calls.
retrynumberNumber of retry attempts (default: 1 for GET).
timeoutnumberRequest timeout in milliseconds.

Isomorphic fetch during SSR ​

fetch() also works in server-side rendering and route handlers. Calls to your app run without an HTTP round-trip.

ts
// src/main.ssr.tsx
import { fetch } from "void/client";
import { defineRender } from "void";

export default defineRender(async (c, assetTags) => {
  // This calls your API route directly with no HTTP request
  const data = await fetch("/api/users");

  const html = renderToString(<App users={data} />);
  return new Response(html, {
    headers: { "content-type": "text/html" },
  });
});

Server-side calls forward the incoming cookie and authorization headers. Headers you set explicitly win.

Next steps ​

implemented.stream AI