RouteDefinition
A RouteDefinition describes one route in your schema. A whole Schema is Record<string, RouteDefinition>.
type RouteDefinition< TMethod extends HttpMethod = HttpMethod, TPath extends string = string, TPathParams = unknown, TSearchParams = unknown, TBody = unknown, TResponse = unknown,> = { method: TMethod; path: TPath; pathParams?: TPathParams; searchParams?: TSearchParams; body?: TBody; response: TResponse; tags?: readonly TagValue<ResolvedParams<TPath, TPathParams, TSearchParams>>[]; invalidatesTags?: readonly TagValue< ResolvedParams<TPath, TPathParams, TSearchParams> >[]; pagination?: PaginationHint;};
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
type ResolvedParams<TPath, TPathParams, TSearchParams> = { pathParams: TPathParams; searchParams: TSearchParams;};Fields
Section titled “Fields”method
Section titled “method”The HTTP method. One of "GET" | "POST" | "PUT" | "PATCH" | "DELETE".
| Method | Cached as a query? | Allowed for useM? | Allowed for useQ? |
|---|---|---|---|
GET | yes | no | yes |
POST | no | yes | no |
PUT | no | yes | no |
PATCH | no | yes | no |
DELETE | no | yes | no |
The type system enforces this — useQ only accepts routes with method: "GET".
A literal string with {paramName} placeholders. The placeholders must match pathParams keys.
{ method: "GET", path: "/facilities/{facilityId}/posts/{postId}", pathParams: {} as { facilityId: string; postId: string },}pathParams
Section titled “pathParams”A type-only declaration of the path parameters. Use {} as { … } to avoid a runtime cost:
pathParams: {} as { facilityId: string; postId: string };All keys are required at the call site. To make a param optional, model it as string | undefined:
pathParams: {} as { facilityId: string; previewToken?: string };searchParams
Section titled “searchParams”Optional. Same {} as { … } pattern; optional properties become optional at the call site.
searchParams: {} as { search?: string; page?: number };undefined values are stripped before request and query-key construction, so they don’t fragment the cache.
Optional, for POST/PUT/PATCH/DELETE. use-q sets Content-Type: application/json and JSON.stringifys the value:
body: {} as { title: string; body: string };For non-JSON bodies (form data, blobs), serialize yourself and pass a headers override per-call.
response
Section titled “response”Required. The successful-response type. Use void for 204 No Content responses:
response: {} as Post; // 200 with JSONresponse: {} as Post[]; // list responseresponse: {} as void; // 204response: {} as { items: Post[]; total: number }; // paginatedA readonly TagValue[]. Each entry labels the cache entry produced by this route (only meaningful for GET routes).
tags: [{ type: "post", facilityId: (p) => p.facilityId }];See Tag invalidation for the full lifecycle.
invalidatesTags
Section titled “invalidatesTags”A readonly TagValue[]. For mutating routes, what tags should be invalidated when the mutation runs (in onSettled).
invalidatesTags: [{ type: "post", facilityId: (p) => p.facilityId }];pagination
Section titled “pagination”A PaginationHint (see below) for paginated routes. Required for useInfiniteQ:
pagination: { kind: "page-number", pageParam: "page", itemsField: "items", totalField: "total",};TagValue
Section titled “TagValue”type TagValue<TParams = unknown> = { type: string; [k: string]: string | number | ((params: TParams) => string | number);};The type field is always a static string. Other fields can be:
- Static:
string | numberliteral value. - Dynamic: a function
(params) => string | numberwhereparamsis the resolved{ pathParams, searchParams }from the call site.
Examples:
// Static{ type: "settings" }
// Mixed{ type: "post", facilityId: (p) => p.facilityId }
// Multiple dynamic fields{ type: "comment", postId: (p) => p.postId, facilityId:(p) => p.facilityId,}See Tag invalidation > Static vs dynamic tags for matching semantics.
PaginationHint
Section titled “PaginationHint”Two variants — page-number and cursor:
type PaginationHint = | { kind: "page-number"; pageParam: string; // searchParams key for the page number itemsField: string; // response field containing items totalField: string; // response field containing total count } | { kind: "cursor"; cursorParam: string; // searchParams key for the cursor itemsField: string; // response field containing items nextCursorField: string; // response field containing next cursor };Page-number example
Section titled “Page-number example”listPosts: { method: "GET", path: "/facilities/{facilityId}/posts", pathParams: {} as { facilityId: string }, searchParams: {} as { page?: number; limit?: number }, response: {} as { items: Post[]; total: number }, pagination: { kind: "page-number", pageParam: "page", itemsField: "items", totalField: "total", },}Cursor example
Section titled “Cursor example”listFeed: { method: "GET", path: "/feed", searchParams: {} as { cursor?: string; limit?: number }, response: {} as { items: Post[]; nextCursor: string | null }, pagination: { kind: "cursor", cursorParam: "cursor", itemsField: "items", nextCursorField: "nextCursor", },}Inferred helpers
Section titled “Inferred helpers”@use-q/api-client exports utility types you can use to introspect a schema:
import type { Schema, RouteKey, ReadableRouteKey, MutatingRouteKey, PaginatedRouteKey, RouteParams, RouteResponse,} from "@use-q/api-client";
type AllRoutes = RouteKey<typeof schema>; // string union of every route nametype Reads = ReadableRouteKey<typeof schema>; // GET-only routestype Writes = MutatingRouteKey<typeof schema>; // non-GET routestype Paginated = PaginatedRouteKey<typeof schema>; // routes with a `pagination` block
type ListPostsParams = RouteParams<typeof schema, "listPosts">;type ListPostsResponse = RouteResponse<typeof schema, "listPosts">;These are how useQ, useM, and friends produce their fully-typed call-site shapes.
Putting it together
Section titled “Putting it together”import type { Schema } from "@use-q/api-client";
interface Post { id: string; facilityId: string; title: string; body: string; createdAt: string;}
export const schema = { listPosts: { method: "GET", path: "/facilities/{facilityId}/posts", pathParams: {} as { facilityId: string }, searchParams: {} as { page?: number; limit?: number }, response: {} as { items: Post[]; total: number }, pagination: { kind: "page-number", pageParam: "page", itemsField: "items", totalField: "total", }, tags: [{ type: "post", facilityId: (p) => p.facilityId }], }, getPost: { method: "GET", path: "/facilities/{facilityId}/posts/{postId}", pathParams: {} as { facilityId: string; postId: string }, response: {} as Post, tags: [ { type: "post", facilityId: (p) => p.facilityId, id: (p) => p.postId }, ], }, createPost: { method: "POST", path: "/facilities/{facilityId}/posts", pathParams: {} as { facilityId: string }, body: {} as { title: string; body: string }, response: {} as Post, invalidatesTags: [{ type: "post", facilityId: (p) => p.facilityId }], },} as const satisfies Schema;