Skip to content

With Orval

Orval generates typed API clients from OpenAPI specs. By default it uses Axios under the hood, so responses have { status, data } — use the adapter option to normalize them.

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

Orval’s generated functions return Axios responses. The adapter maps res.databody so expect-status can read it.

import { expectStatus } from "@/lib/expect-status";
import { createPet, getPetById } from "@/api/petstore";
const pet = await expectStatus(200, getPetById(1));
// ^? Pet
const created = await expectStatus(
201,
createPet({ name: "Buddy", tag: "dog" }),
);
const pet = await expectStatus(201, createPet(data), {
400: "Invalid pet data.",
409: (body) => redirect(`/pets/${body.id}`),
"5xx": "Service unavailable.",
});

Orval can generate TanStack Query hooks. Use expect-status inside custom hooks or wrap the generated ones:

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { expectStatus } from "@/lib/expect-status";
import { getPetById, createPet } from "@/api/petstore";
function usePet(id: number) {
return useQuery({
queryKey: ["pet", id],
queryFn: () => expectStatus(200, getPetById(id)),
});
}
function useCreatePet() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreatePetBody) =>
expectStatus(201, createPet(data), {
409: "A pet with that name already exists.",
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["pets"] });
},
});
}
const result = await expectStatus(200, getPetById(1), { throws: false });
if (result.ok) {
renderPet(result.data);
} else {
showError(result.error.message);
}

Minimal orval.config.ts for a typed Axios client:

import { defineConfig } from "orval";
export default defineConfig({
petstore: {
input: "./petstore.yaml",
output: {
target: "./src/api/petstore.ts",
client: "axios",
override: {
mutator: {
path: "./src/api/axios-instance.ts",
name: "customInstance",
},
},
},
},
});
src/api/axios-instance.ts
import axios from "axios";
export const customInstance = axios.create({
baseURL: "https://api.example.com",
validateStatus: () => true, // let expect-status handle errors
});
export default customInstance;