Askr
Server & APIs

Typed Clients

Use typed clients with the current published Askr packages.

  • @askrjs/fetch0.0.1

How to use typed clients

Describe endpoint input and result once, then create a client whose calls accept typed params, query, headers, body, and AbortSignal.

  1. Import the published @askrjs/fetch entrypoint.
  2. Keep typed clients configuration next to the component, route, or server composition root that owns it.
  3. Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import { createClient, defineApi, get, json } from '@askrjs/fetch';

const definition = defineApi({
  getProject: get('/projects/{id}')
    .params<{ id: string }>()
    .returns(json<Project>()),
});
const api = createClient(definition, { baseUrl: env.API_URL });

const result = await api.getProject({ params: { id }, signal });
if (result.ok) renderProject(result.data);

Define endpoints

get(path), post(path), and the other method builders return an EndpointBuilder that you refine by chaining .params<T>(spec?), .query<T>(spec?), .headers<T>(spec?), .body(codec), .returns(codec) or .returns(status, codec), and .errors({status: codec}). defineApi(endpoints, metadata?) accepts a plain object of these builders — or already-built EndpointDescriptor values — and returns the ApiDefinition that createClient() consumes.

Create a client

createClient(api, options) builds a client from ClientOptions: baseUrl, a custom fetch implementation, default headers, credentials, a default timeout, and a middleware array. The returned object exposes one method per endpoint key, so client.getUser(...) exists only if you named an endpoint getUser in defineApi().

Typed input

Each client method takes a single input object assembled from whichever of params, query, headers, and body the endpoint declared. params and body are required whenever the endpoint has them; query and headers are optional unless every one of their fields is required. When an endpoint needs none of these, its method's input argument itself becomes optional, so client.health() can be called with no arguments at all.

Typed output

A call resolves to a discriminated ClientResult: a SuccessResult keyed by whichever status you registered with .returns(), an HttpResult keyed by whichever status or default you registered with .errors(), or a FailureResult for anything below the HTTP layer — a network error, a timeout, a caller abort, a decode failure, or a middleware error. Narrowing on result.ok or result.kind gives you the exact typed data or error shape for that branch without a cast.