fetcher()
fetcher()
The main function for making type-safe HTTP requests with schema validation and error handling.
Signature
function fetcher<TResponse extends StandardSchemaV1 | undefined = undefined>( input: RequestInfo | URL, init?: FetcherRequestInit<TResponse>): Promise<InferResponse<TResponse>>Parameters
input: RequestInfo | URL
The URL or Request object to fetch from. This parameter works exactly like the first parameter of the standard fetch() function.
Examples:
// String URLawait fetcher('/api/users');
// URL objectawait fetcher(new URL('/api/users', 'https://api.example.com'));
// Request objectconst request = new Request('/api/users', { method: 'POST' });await fetcher(request);init?: FetcherRequestInit<TResponse>
Optional configuration object that extends the standard RequestInit with additional properties.
Standard RequestInit Properties
All standard fetch() options are supported:
method?: string- HTTP method (GET, POST, PUT, DELETE, etc.)headers?: HeadersInit- Request headersbody?: BodyInit- Request bodymode?: RequestMode- CORS modecredentials?: RequestCredentials- Credentials handlingcache?: RequestCache- Cache controlredirect?: RequestRedirect- Redirect handlingreferrer?: string- Referrer URLreferrerPolicy?: ReferrerPolicy- Referrer policyintegrity?: string- Subresource integritykeepalive?: boolean- Keep connection alivesignal?: AbortSignal- Abort signal for cancellationwindow?: null- Window object (must be null)
Extended Properties
schema?: TResponse- Response validation schemaerrors?: ApiErrorStatic<any>[]- Custom error classes for error handling
Return Value
Returns a Promise that resolves to the validated response data. The return type is automatically inferred from the schema:
- If no schema is provided:
Promise<any> - If schema is provided:
Promise<InferResponse<TSchema>>
Examples
Basic Usage
// Simple GET requestconst data = await fetcher('/api/users');
// POST request with bodyconst user = await fetcher('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'John Doe', email: 'john@example.com' })});With Schema Validation
import { z } from 'zod';
const UserSchema = z.object({ id: z.string(), name: z.string(), email: z.string().email()});
// Type-safe responseconst user = await fetcher('/api/users/123', { schema: UserSchema});
// user is typed as { id: string; name: string; email: string }console.log(user.name); // TypeScript knows this is a stringWith Error Handling
import { defineError } from '@shkumbinhsn/fetcher';
const NotFoundError = defineError(404, z.object({ message: z.string(), resource: z.string()}));
const ValidationError = defineError(400, z.object({ errors: z.array(z.object({ field: z.string(), message: z.string() }))}));
try { const user = await fetcher('/api/users/999', { schema: UserSchema, errors: [NotFoundError, ValidationError] });} catch (error) { if (error instanceof NotFoundError) { console.log(`Not found: ${error.data.resource}`); } else if (error instanceof ValidationError) { error.data.errors.forEach(err => console.log(`${err.field}: ${err.message}`) ); }}Authentication
const user = await fetcher('/api/users/me', { headers: { 'Authorization': 'Bearer your-jwt-token' }, schema: UserSchema});Request Timeout
const controller = new AbortController();const timeoutId = setTimeout(() => controller.abort(), 5000);
try { const data = await fetcher('/api/slow-endpoint', { signal: controller.signal, schema: DataSchema });} catch (error) { if (error.name === 'AbortError') { console.log('Request timed out'); }} finally { clearTimeout(timeoutId);}File Upload
const formData = new FormData();formData.append('file', fileInput.files[0]);formData.append('name', 'Document Name');
const uploadResult = await fetcher('/api/upload', { method: 'POST', body: formData, schema: z.object({ id: z.string(), url: z.string(), size: z.number() })});Query Parameters
const params = new URLSearchParams({ page: '1', limit: '10', search: 'john'});
const users = await fetcher(`/api/users?${params}`, { schema: z.object({ users: z.array(UserSchema), pagination: z.object({ page: z.number(), total: z.number() }) })});Error Handling
The function throws different types of errors based on the situation:
API Errors (with custom error classes)
When custom error classes are provided and match the response status:
try { await fetcher('/api/users/999', { errors: [NotFoundError] });} catch (error) { if (error instanceof NotFoundError) { // error.data contains validated error response data // error.statusCode contains the HTTP status code // error.response contains the original Response object }}Validation Errors
When response validation fails:
try { await fetcher('/api/users/123', { schema: UserSchema });} catch (error) { // Generic Error with message "Response validation failed: ..." console.log(error.message);}Network Errors
When the request fails at the network level:
try { await fetcher('/api/users');} catch (error) { // Generic Error with message "Request failed: ..." console.log(error.message);}Generic HTTP Errors
When no custom error classes are provided for the status code:
try { await fetcher('/api/users/999'); // Returns 404} catch (error) { // Generic Error with message "Request failed: 404 Not Found" console.log(error.message);}Special Response Handling
Empty Responses (204 No Content)
// Returns null for 204 responses or empty contentconst result = await fetcher('/api/users/123', { method: 'DELETE'}); // result is nullNon-JSON Responses
The function expects JSON responses. For other content types, use the standard fetch() function:
// For text responsesconst response = await fetch('/api/text-endpoint');const text = await response.text();
// For blob responsesconst response = await fetch('/api/file-download');const blob = await response.blob();TypeScript Integration
The function provides full TypeScript support with automatic type inference:
// Response type is inferred from schemaconst user = await fetcher('/api/users/123', { schema: UserSchema}); // Type: { id: string; name: string; email: string }
// Without schema, type is anyconst data = await fetcher('/api/users/123'); // Type: any
// Custom request init typesinterface CustomInit<T> extends FetcherRequestInit<T> { retries?: number;}
async function customFetch<T>(url: string, init?: CustomInit<T>) { // Custom logic here return fetcher(url, init);}Related
defineError()- Create custom error classesFetcherRequestInit- Request configuration typeInferResponse- Response type inference