chowbea-axios
Usage

Type Helpers

Extract and use types from your OpenAPI schema.

You need a type for a form field or a hook return value, and you're about to write interface User by hand. Stop.

Type helpers extract request bodies, response types, and schema models from your OpenAPI spec. But first — check whether a named contract already exists. It usually does.

Prefer api.contracts.ts for day-to-day types. Named interfaces like UserDto, CreateUserBody, and ListUsersQueryParams are generated for every schema and operation — import those first. Use the helpers below when you need path/operation-based extraction or no named contract exists yet.

Two Approaches

Two ways to reference an endpoint when extracting types. Same output — pick what matches how you think about the API.

Path-Based

Reference the URL path template and HTTP method — exactly as in your OpenAPI spec. Natural if you think "I'm calling GET /users/{id}."

Helpers: ApiRequestBody, ApiResponseData, ApiPathParams, ApiQueryParams, ApiStatusCodes

Operation-Based

Reference the operationId from your spec. Cleaner when operations are well-named (getUserById, createOrder).

Helpers: ServerRequestBody, ServerRequestParams, ServerResponseType

Not sure which to use? Check api.contracts.ts first. If UserDto exists, import it. Helpers are for when it doesn't — or when you're extracting by path/method and don't want to grep the contracts file.


Extract Request Body

type CreateUserInput = ApiRequestBody<"/users", "post">;
// { name: string; email: string }
type CreateUserInput = ServerRequestBody<"createUser">;
// { name: string; email: string }

If CreateUserBody exists in contracts, just import it. These helpers are the fallback.

Extract Response Type

type User = ApiResponseData<"/users/{id}", "get">;

// With specific status code
type CreatedUser = ApiResponseData<"/users", "post", 201>;
type User = ServerResponseType<"getUserById">;

// With specific status code
type NotFound = ServerResponseType<"getUserById", 404>;

Extract Parameters

Path Parameters

type UserParams = ApiPathParams<"/users/{id}">;
// { id: string | number }

Query Parameters

type ListQuery = ApiQueryParams<"/users", "get">;
// { limit?: number; offset?: number }
type Params = ServerRequestParams<"getUserById">;
// { path: { id: string }; query?: { include?: string[] } }

Combines path and query params in one type.

Extract Status Codes

type UserStatusCodes = ApiStatusCodes<"/users/{id}", "get">;
// 200 | 404 | 500

Schema Models

Preferred — import from contracts:

import type {
  UserDto,
  PostDto,
  PaginatedUserResponseDto,
} from "@/services/api/_generated/api.contracts";

type User = UserDto;
type Post = PostDto;
type PaginatedUsers = PaginatedUserResponseDto;

This is what The Query Layer uses. Named interfaces, cmd+click navigation, no string keys to misremember.

ServerModel still works for a string-keyed lookup into components.schemas, but you're choosing the scenic route:

type User = ServerModel<"User">;
type Post = ServerModel<"Post">;
type PaginatedUsers = ServerModel<"PaginatedUserResponse">;

If both exist, pick contracts. Your future self grepping for UserDto will thank you.


Utility Helpers

Paths & Methods

type AllPaths = Paths;
// "/users" | "/users/{id}" | "/posts" | ...

type Method = HttpMethod;
// "get" | "post" | "put" | "delete" | "patch"

Better IDE Tooltips

// Expand shows full structure instead of type reference
type ExpandedUser = Expand<User & { posts: Post[] }>;

// ExpandRecursively goes through nested types
type DeepUser = ExpandRecursively<UserWithRelations>;

Examples

Form with typed state
import type { CreateUserBody } from "@/services/api/_generated/api.contracts";

const [form, setForm] = useState<CreateUserBody>({
  name: "",
  email: "",
});
Custom hook with typed response
import type { UserDto } from "@/services/api/_generated/api.contracts";

function useUser(id: string) {
  const [user, setUser] = useState<UserDto | null>(null);
  // ...
}
Service layer
import type {
  CreateUserBody,
  UserDto,
} from "@/services/api/_generated/api.contracts";

export const userService = {
  create: (input: CreateUserBody) => api.op.createUser(input),
  getById: (id: string) => api.op.getUserById({ id }),
};

type User = UserDto;

In a real app, this service layer lives inside a query class — see The Query Layer.

Next Steps

On this page