Skip to content

Instantly share code, notes, and snippets.

@nikoheikkila
Created February 17, 2026 19:20
Show Gist options
  • Select an option

  • Save nikoheikkila/fd4880b57094ac323d2eb263f8e18c8d to your computer and use it in GitHub Desktop.

Select an option

Save nikoheikkila/fd4880b57094ac323d2eb263f8e18c8d to your computer and use it in GitHub Desktop.
Zod-powered HTTP requests with Node.js fetch()
import { z } from "zod";
interface FetchResponse<O = unknown> {
ok: boolean;
status: number;
json(): Promise<O>;
}
interface FetchOptions {
url: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
headers: Record<string, string>;
body: unknown;
fetch(url: string, init?: RequestInit): Promise<FetchResponse>;
}
const withSchema = <I, O>(schema: z.ZodSchema<O, I>) => async (options: FetchOptions) => {
const { url, method, headers, fetch } = options;
const body = options.body
? JSON.stringify(options.body)
: undefined;
const response = await fetch(url, {
method,
headers,
body,
});
if (!response.ok) {
throw new Error(`HTTP status: ${response.status}`);
}
const json = await response.json();
const result = await schema.safeParseAsync(json);
if (!result.success) {
throw new Error(`Invalid response: ${z.prettifyError(result.error)}`);
}
return {
status: response.status,
data: result.data,
};
}
/** TEST **/
const apiSchema = z.object({
status: z.enum(['success', 'error']),
message: z.url(),
});
const fetchFromApi = withSchema(apiSchema);
const response = await fetchFromApi({
url: "https://dog.ceo/api/breeds/image/random",
method: "GET",
headers: {
"Accept": "application/json",
},
body: undefined,
fetch,
});
console.log(`HTTP Response ${response.status}`);
console.dir(response.data);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment