Basic Usage
Simple GET Request
typescript
import { typedFetch, NotFoundError, UnauthorizedError } from '@pbpeterson/typed-fetch';
interface User {
id: number;
name: string;
email: string;
}
type ExpectedErrors = NotFoundError | UnauthorizedError;
const { response, error } = await typedFetch<User[], ExpectedErrors>('/api/users');
if (error) {
if (error instanceof NotFoundError) {
console.log('Users endpoint not found');
} else if (error instanceof UnauthorizedError) {
console.log('Authentication required');
} else {
console.error('Unexpected error:', error.statusText);
}
} else {
const users = await response.json(); // Type: User[]
}Specifying Expected Client Errors
Important
Specify which client errors (4xx) you expect. Without the second generic, the error union includes all 29 client error types, making handling verbose.
typescript
// ❌ Without specifying — includes ALL client errors
const { error } = await typedFetch<User[]>('/api/users');
// error: BadRequestError | UnauthorizedError | ... (29 types) | ServerErrors | NetworkError | null
// ✅ Specify only what the endpoint can return
type ExpectedErrors = NotFoundError | UnauthorizedError;
const { error: betterError } = await typedFetch<User[], ExpectedErrors>('/api/users');
// error: NotFoundError | UnauthorizedError | ServerErrors | NetworkError | nullWhy Server Errors and Network Errors are always included:
- Server Errors (5xx) — Cannot be controlled by the client.
- Network Errors — Connection issues, timeouts, and DNS failures are always possible.
POST Request with Body
typescript
import { typedFetch, BadRequestError } from '@pbpeterson/typed-fetch';
interface ValidationError {
field: string;
message: string;
}
const { response, error } = await typedFetch<User, BadRequestError>('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'John', email: 'john@example.com' }),
});
if (error) {
if (error instanceof BadRequestError) {
// json<T>() accepts a type parameter for typed error bodies
const errors = await error.json<ValidationError[]>();
console.error('Validation failed:', errors);
}
} else {
const user = await response.json();
}Using Type Guards
Use isHttpError() and isNetworkError() instead of instanceof for reliable checks, especially across package boundaries:
typescript
import { typedFetch, isHttpError, isNetworkError } from '@pbpeterson/typed-fetch';
const { error } = await typedFetch<User[]>('/api/users');
if (error) {
if (isHttpError(error)) {
console.log(`HTTP ${error.status}: ${error.statusText}`);
console.log('Error name:', error.name); // e.g. "NotFoundError"
} else if (isNetworkError(error)) {
console.log('Connection failed:', error.message);
}
}Cloning Errors for Multiple Body Reads
typescript
const { error } = await typedFetch<User[]>('/api/users');
if (error && isHttpError(error)) {
const cloned = error.clone();
const errorJson = await error.json();
const errorText = await cloned.text();
console.log('JSON:', errorJson);
console.log('Text:', errorText);
}Request Cancellation and Timeout
Same API as native fetch — all options work:
typescript
// Cancellation
const controller = new AbortController();
const { response, error } = await typedFetch<User[]>('/api/users', {
signal: controller.signal,
});
controller.abort();
// Timeout
const { response: r, error: e } = await typedFetch<User[]>('/api/users', {
signal: AbortSignal.timeout(5000),
});