Skip to content

With Native Fetch

Two approaches: fetchExpect (one-liner helper) or the async adapter (instance-level, full control).

fetchExpect calls fetch, parses JSON, and validates the status in one call:

import { fetchExpect } from "expect-status/fetch";
type UserResponse =
| { status: 200; body: { id: string; name: string } }
| { status: 404; body: { message: string } };
const user = await fetchExpect<UserResponse>(
"https://api.example.com/users/1",
200,
);
// ^? { id: string; name: string }
const user = await fetchExpect<UserResponse>(
"https://api.example.com/users/1",
200,
{
init: {
headers: { Authorization: `Bearer ${token}` },
},
404: "User not found.",
"5xx": "Service unavailable.",
},
);
type CreateUserResponse =
| { status: 201; body: { id: string } }
| { status: 409; body: { message: string } };
const created = await fetchExpect<CreateUserResponse>(
"https://api.example.com/users",
201,
{
init: {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice" }),
},
409: "User already exists.",
},
);

For apps that use fetch everywhere, create a shared instance with an async adapter:

import { createExpectStatus, adapters } from "expect-status";
export const expectStatus = createExpectStatus({
adapter: adapters.fetch,
fallbackMessage: "Request failed.",
defaults: {
401: "Please sign in.",
"5xx": "Service unavailable.",
},
});

Then use it with bare fetch calls:

const user = await expectStatus(200, fetch("/api/users/1"));
const created = await expectStatus(
201,
fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice" }),
}),
{
409: "User already exists.",
},
);
fetchExpectAsync adapter
SetupNone — import and callOne-time instance config
Instance defaults✅ (shared messages, hooks, groups)
ObservabilityPer-call onlyInstance-wide onError / onSuccess
Best forOne-off calls, scriptsApp-wide fetch usage

Works with both approaches:

const result = await fetchExpect<UserResponse>(url, 200, { throws: false });
if (result.ok) {
renderUser(result.data);
} else {
showError(result.error.message);
}