Quick start
This guide walks through a complete, runnable example: define a small schema, create a client, mount the provider, and use useQ / useM from a component.
We’ll use a fictional API with three resources — facilities, posts, and comments — and a single facilityId path parameter. The same example is reused across the docs.
-
Install the packages
Terminal window pnpm add @use-q/api-client @use-q/api-client-react @tanstack/react-query react react-domSee Installation for npm/yarn equivalents and peer deps.
-
Define a schema
Create
src/api/schema.ts:import type { Schema } from "@use-q/api-client";export interface Post {id: string;facilityId: string;title: string;body: string;createdAt: string;}export interface CreatePostInput {title: string;body: string;}export const schema = {listPosts: {method: "GET",path: "/facilities/{facilityId}/posts",pathParams: {} as { facilityId: string },searchParams: {} as { search?: string },response: {} as Post[],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", id: (p) => p.postId, facilityId: (p) => p.facilityId },],},createPost: {method: "POST",path: "/facilities/{facilityId}/posts",pathParams: {} as { facilityId: string },body: {} as CreatePostInput,response: {} as Post,invalidatesTags: [{ type: "post", facilityId: (p) => p.facilityId }],},} as const satisfies Schema; -
Create the client
In
src/api/client.ts, instantiate the client once and re-export the hooks you’ll use across the app:import { createApiClient } from "@use-q/api-client-react";import { schema } from "./schema";export const api = createApiClient<typeof schema>(schema, {baseUrl: import.meta.env.VITE_API_BASE_URL ?? "https://api.example.com",defaultHeaders: () => ({Authorization: `Bearer ${localStorage.getItem("token") ?? ""}`,}),});export const { useQ, useM, useInfiniteQ, useQClient, queryClient } = api; -
Wrap your app in
QueryClientProvidercreateApiClientreturns the underlyingqueryClient— use it directly so every hook shares the same cache:import { StrictMode } from "react";import { createRoot } from "react-dom/client";import { QueryClientProvider } from "@tanstack/react-query";import { api } from "./api/client";import { App } from "./App";createRoot(document.getElementById("root")!).render(<StrictMode><QueryClientProvider client={api.queryClient}><App /></QueryClientProvider></StrictMode>,); -
Read with
useQimport { useQ } from "./api/client";export function PostList({ facilityId }: { facilityId: string }) {const { data, isLoading, error } = useQ("listPosts", {pathParams: { facilityId },searchParams: { search: "" },});if (isLoading) return <p>Loading posts…</p>;if (error) return <p>Failed to load: {error.message}</p>;return (<ul>{data?.map((post) => (<li key={post.id}>{post.title}</li>))}</ul>);} -
Write with
useMimport { useM } from "./api/client";export function NewPostForm({ facilityId }: { facilityId: string }) {const createPost = useM("createPost", { pathParams: { facilityId } });return (<formonSubmit={(e) => {e.preventDefault();const form = new FormData(e.currentTarget);createPost.mutate({body: {title: String(form.get("title")),body: String(form.get("body")),},});}}><input name="title" placeholder="Title" required /><textarea name="body" placeholder="Body" required /><button type="submit" disabled={createPost.isPending}>{createPost.isPending ? "Saving…" : "Publish"}</button></form>);}Because
createPost.invalidatesTagsmatcheslistPosts.tags, the list refetches automatically once the mutation settles — no manual invalidation required.
What just happened?
Section titled “What just happened?”- The
schemaobject is the single source of truth. TypeScript infers all path params, search params, body, and response types from it. createApiClientbuilt aQueryClient, aTagRegistry, and a bundle of hooks bound to your schema.useQ("listPosts", …)produced a query key of the form["api", "GET", "/facilities/abc/posts", { search: "" }]so hierarchical invalidation just works.useM("createPost", …)ran the mutation, then asked the registry for every query whose tags matched and invalidated them inonSettled.
Next steps
Section titled “Next steps”- Schema definition — a full tour of every
RouteDefinitionfield. useQ— every option, includingselect,enabled, andrefetchInterval.useM— optimistic updates and tag chaining.useQClient— manual cache control.