Skip to content
GreenSnippets

Typed fetch helper that checks the response

A fetch wrapper that throws on HTTP errors and validates the JSON body with a type guard.

TypeScript
class HttpError extends Error {
  readonly status: number;
  constructor(status: number, message: string) {
    super(message);
    this.status = status;
  }
}

async function fetchJson<T>(
  url: string,
  guard: (value: unknown) => value is T,
  init?: RequestInit,
): Promise<T> {
  const response = await fetch(url, { ...init, headers: { Accept: "application/json", ...init?.headers } });
  if (!response.ok) throw new HttpError(response.status, `${response.status} ${response.statusText}`);
  const data: unknown = await response.json();
  if (!guard(data)) throw new Error(`Unexpected response shape from ${url}`);
  return data;
}

Example / expected output

Output
const user = await fetchJson("/api/me", (v): v is { id: string } => typeof (v as any)?.id === "string");

How it works

response.json() is typed as any, so a changed API silently breaks your code later. This helper throws a descriptive error for non-2xx responses and verifies the body with a guard before returning it typed. Combine it with a timeout (see the fetch-with-timeout snippet) for production use.