Quick Start
Install
Section titled “Install”bash npm install expect-status bash pnpm add expect-status bash yarn add expect-status bash bun add expect-status Requires TypeScript 5.4+ and Node 18+ (or Deno / Bun / Cloudflare Workers).
The response shape
Section titled “The response shape”expect-status works with any client that returns a status-discriminated union:
type CreateOrgResponse = | { status: 201; body: { id: string; name: string } } | { status: 409; body: { message: string; orgId: string } } | { status: 422; body: { message: string; field: string } };This is the default output of ts-rest, orval, hey-api, and openapi-fetch. For Axios or custom shapes, use the adapter option.
Basic usage
Section titled “Basic usage”import { expectStatus } from "expect-status";
const org = await expectStatus(201, client.createOrg({ body: data }));// ^? { id: string; name: string }If the status isn’t 201, expect-status throws with a message extracted from the response body.
Multiple success statuses
Section titled “Multiple success statuses”const result = await expectStatus([200, 201], client.upsert({ body: data }));// ^? body of 200 | body of 201Named specifiers
Section titled “Named specifiers”await expectStatus("success", client.health()); // any 2xxawait expectStatus("!4xx", client.maybeRedirect()); // anything except 400-499await expectStatus([200, "3xx"], response); // 200 or any 300-399Flat dispatch
Section titled “Flat dispatch”Strings are messages, functions are handlers:
const org = await expectStatus(201, client.createOrg({ body: data }), { 409: ({ orgId }) => redirect(`/org/${orgId}`), // handler — body is typed 422: "Please check your input.", // message — auto-throws "5xx": "Service is temporarily unavailable.", // range — catches 500-599});Handlers can return values, widening the return type:
const result = await expectStatus(201, client.createOrg({ body: data }), { 409: (body) => ({ conflict: true, orgId: body.orgId }),});// result: { id: string; name: string } | { conflict: true; orgId: string }Recover & SafeResult
Section titled “Recover & SafeResult”Two ways to avoid throwing:
// recover — catch-all that returns a fallbackconst flags = await expectStatus(200, client.getFlags(), { recover: () => DEFAULT_FLAGS,});
// throws: false — typed discriminated unionconst result = await expectStatus(200, client.getUser(id), { throws: false });if (result.ok) { result.data; // typed body} else { result.error; // ExpectStatusError}Instance defaults
Section titled “Instance defaults”Configure shared behaviour across your app:
import { createExpectStatus } from "expect-status";
export const expectStatus = createExpectStatus({ fallbackMessage: "Something went wrong.", groups: { auth: [401, 403] }, defaults: { auth: "Please sign in or check your permissions.", "5xx": "Service is temporarily unavailable.", }, onError: (err, res) => { Sentry.captureException(err, { extra: { status: res.status } }); },});Per-call dispatch always shadows instance defaults.
Adapter
Section titled “Adapter”For clients that don’t return { status, body } (e.g. Axios), normalize at the instance level:
const expectStatus = createExpectStatus({ adapter: (res) => ({ status: res.status, body: res.data }),});
const org = await expectStatus(201, axios.post("/orgs", data));Next steps
Section titled “Next steps”- Examples — every feature with copy-pastable code
- Flat Dispatch — dispatch keys, priority rules, and specificity
- Status Specifiers — full reference table
- Error Resolution — the complete resolution chain
- API Reference — full signatures and options