Every TypeScript developer has written this:
try {
const response = await fetch('/api/users');
const users = await response.json();
} catch (error) {
// Network error? 404? 500? Parsing error? Who knows!
}
The problem? HTTP errors don't throw. A 404 silently passes through. Network errors and parsing errors get lumped together. You
end up with fragile, hard-to-read code.
What if errors were just values?
Inspired by Go's error handling, I built typed-fetch — a thin wrapper over the native Fetch API that never throws.
import { typedFetch, isNetworkError, UnauthorizedError } from '@pbpeterson/typed-fetch';
const { response, error } = await typedFetch<User[], UnauthorizedError>('/api/users');
if (error) {
if (isNetworkError(error)) {
console.log('No connection — are you offline?');
} else if (error instanceof UnauthorizedError) {
console.log('Session expired — redirecting to login');
window.location.href = '/login';
} else {
console.log(`HTTP ${error.status}: ${error.statusText}`);
}
} else {
const users = await response.json(); // Type: User[]
}
No try/catch. No guessing. TypeScript knows exactly what you're working with.
Features
- Never throws — all errors returned as values
- Fully typed — complete TypeScript support with literal status types
- 40 HTTP error classes — every standard status code (400–511)
-
Network error handling — separate
NetworkErrorclass for connection issues - Built on Fetch — same API you already know
- Minimal — only one dependency
Install
pnpm install @pbpeterson/typed-fetch
Why not just check response.ok?
You could — but you lose type safety, you still need try/catch for network errors, and you have to build your own error handling
every time. typed-fetch gives you a consistent pattern with typed errors out of the box.
If you've ever been bitten by an unhandled fetch error in production, give it a try. Feedback and contributions welcome!
Top comments (0)