Introduction
use-q is an opinionated, schema-driven way to use TanStack Query v5 — TkDodo's Practical React Query patterns as a typed API client.
use-q is an opinionated way to use TanStack Query v5. The opinions are Dominik Dorfmeister (TkDodo)'s, from the Practical React Query series — encoded as a thin, schema-driven API client.
You describe your API once as a plain object of RouteDefinitions. From that single source of truth, use-q infers path params, search params, request bodies, and response shapes, builds hierarchical query keys, and wires up cache invalidation automatically through schema-defined tags. No any leaks into your components, and no query keys are written by hand.
You still use TanStack Query. use-q does not replace it or hide the QueryClient — it makes the recommended patterns the path of least resistance. See Practical React Query for how each API maps to TkDodo's posts.
The packages
| Package | What it provides |
|---|---|
@use-q/api-client | Framework-agnostic core: createFetcher, the Schema / RouteDefinition types, and error helpers. Zero runtime dependencies. |
@use-q/api-client-react | createApiClient and the React hooks (useQ, useM, useInfiniteQ, useSuspenseQ, useQClient) on top of TanStack Query v5. |
@use-q/api-client-codegen | Generates a typed schema from an OpenAPI 3.x spec. Ships the use-q-codegen CLI bin. |
The core runs anywhere fetch does — Node, Bun, Deno, edge workers, or the browser — so you can share one schema between server actions, loaders, scripts, and your React app.
A quick taste
Define a schema and create a client:
import { createApiClient } from "@use-q/api-client-react";
import type { RouteDefinition } from "@use-q/api-client";
interface Post {
id: string;
title: string;
}
const schema = {
listPosts: {
method: "GET",
path: "/facilities/{facilityId}/posts",
tags: ["posts"],
} satisfies RouteDefinition<{ facilityId: string }, never, never, Post[]>,
createPost: {
method: "POST",
path: "/facilities/{facilityId}/posts",
invalidatesTags: ["posts"],
} satisfies RouteDefinition<{ facilityId: string }, never, { title: string }, Post>,
} as const;
export const api = createApiClient(schema, {
baseUrl: "https://api.example.com",
});
export const { useQ, useM, useQClient } = api;Then use it in a component:
function PostList({ facilityId }: { facilityId: string }) {
const { data, isLoading } = api.useQ("listPosts", { params: { facilityId } });
const createPost = api.useM("createPost");
if (isLoading) return <p>Loading…</p>;
return (
<>
{data?.map((p) => <article key={p.id}>{p.title}</article>)}
<button
onClick={() =>
createPost.mutate({ params: { facilityId }, body: { title: "New post" } })
}
>
Add
</button>
</>
);
}That's it — the list refetches automatically after the mutation, because createPost.invalidatesTags matches listPosts.tags.
Design principles
These are TkDodo's Practical React Query defaults, as a library:
- End-to-end type safety. Path params, search params, request bodies, and response shapes are all inferred from a single
RouteDefinitionmap — not fromuseQuerygenerics. - A query abstraction, not a new mental model.
createApiClientis the colocatedqueryKey+queryFnlayer. You still mountQueryClientProviderand passstaleTime/select/enabled. - Hierarchical query keys you never type twice. Keys are always
["api", method, path, searchParams]. Prefix invalidation and aqueryKeysfactory come for free. - Schema-driven invalidation. Tag routes with
tags/invalidatesTagsand mutations refresh the right queries inonSettledthrough a sharedTagRegistry. - Optimistic updates that compose. Update multiple caches per mutation with snapshot/rollback semantics built in. Combine with
additionalInvalidatesTagsfor arbitrary fan-out. - Framework-agnostic core.
@use-q/api-clientis a tiny zero-dependency fetcher that runs in Node, Bun, Deno, edge workers, or the browser. Use it from server actions, loaders, or scripts — no React required. - OpenAPI codegen. Generate a typed schema directly from an OpenAPI 3.x spec with
use-q-codegenfrom@use-q/api-client-codegen. Pagination is detected automatically. - Bring your own QueryClient. Reuse a single
QueryClientacross multiplecreateApiClientinstances, or hook into persistence and devtools —use-qnever gets in the way.
Where to next?
The opinions — and links to TkDodo's series
Install @use-q/api-client and the React layer
Build a typed list + create flow
Anatomy of a RouteDefinition
LLM-readable docs
Agents can read these docs at /llms.txt (an index of every page) or /llms-full.txt (the complete corpus in one file). Each page is also available as Markdown by appending .md to its URL.