Typed API client
| Type the responses | |
| GET helper | |
| POST helper | |
| Throw typed errors |
Type the responses
A typed client starts with response shapes written as plain interfaces. Every helper then promises exactly that shape, so screens get completion instead of guessing field names.
Keep shapes narrow and close to what the screen needs. Small response types expose server drift early, while giant shared types hide it until runtime.
interface User {
id: string;
name: string;
}
interface ApiError {
status: number;
message: string;
}
GET helper
One async GET helper can serve every read endpoint. It takes a path, checks the status, and returns parsed JSON cast to the expected generic type.
Call sites stay tiny because the helper owns headers, status checks, and parsing. Reading data looks like calling a function, not juggling low level response details.
async function apiGet<T>(path: string): Promise<T> {
const res = await fetch(path);
if (!res.ok) {
throw new Error("GET failed: " + res.status);
}
return res.json() as Promise<T>;
}
const user = await apiGet<User>("/users/7");
POST helper
Writes need the same care with one extra input: the request body. A POST helper stringifies the payload, sets the JSON header, and returns the created resource type.
Mirroring GET keeps the client predictable. Two helpers with the same error style cover most small apps without a class hierarchy or extra runtime.
async function apiPost<T>(path: string, body: unknown): Promise<T> {
const res = await fetch(path, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
if (!res.ok) {
throw new Error("POST failed: " + res.status);
}
return res.json() as Promise<T>;
}
Throw typed errors
Network code fails for honest reasons: lost connection, missing record, or invalid input. Throw shaped errors with status plus message so handlers can branch without parsing text.
Catch at the screen level where a fallback exists, not inside every helper. Helpers report precisely, screens decide between retry, redirect, and a calm message.
function toApiError(status: number, message: string): ApiError {
return { status, message };
}
throw toApiError(404, "User not found");
Next: Hand-rolled validation
Article author: Arthur Isaev