Documentation

Getting Started

Learn what micro-rq solves, then define and use your first REST resource.

Core rule

micro-rq generates TanStack Query config. You still use TanStack Query options directly in useQuery and useMutation.

What is micro-rq?

micro-rq helps you define a REST endpoint once and reuse it everywhere.

It does not replace TanStack Query. It prepares the repeated request pieces for you, such as queryKey, queryFn, mutationFn, URLs, headers, parsing, auth, and token refresh.

You still use TanStack Query normally with useQuery, useMutation, invalidateQueries, and your usual query options.

Key points

  • Describe each REST endpoint once.
  • Let TanStack Query handle caching and hook behavior.
  • Move request setup out of UI components.
  • Keep UI components focused on using hooks.

The problem it solves

In many REST apps, one endpoint is described in too many places. The URL may live in one file, the query key in another, and the invalidation logic in a third.

That repeated work is not really TanStack Query logic. It is the REST setup around TanStack Query.

micro-rq keeps that setup in one place so each endpoint has one typed definition and one stable query key shape.

The example below shows the problem first, then shows how this package solves it.

TypeScript
1// In many REST apps, one endpoint is still described in several places.2useQuery({3  queryKey: ["users", "detail", userId],4  queryFn: async () => {5    const response = await fetch(`/api/users/${userId}`, {6      headers: {7        Authorization: `Bearer ${token}`,8      },9    });10 11    if (!response.ok) {12      throw new Error("Request failed");13    }14 15    return response.json() as Promise<User>;16  },17  enabled: Boolean(userId),18});19 20// With micro-rq, the endpoint owns URL, fetch, parsing, and key shape.21useQuery({22  ...users.detail.toQuery(userId),23  enabled: Boolean(userId),24});

Install

Install micro-rq next to TanStack Query. TanStack Query is a peer dependency because your app still uses it directly.

TypeScript
1npm install micro-rq @tanstack/react-query

Step 1: Create an API client

Start by creating one API client.

This client holds the request settings that many endpoints share, such as the base URL and global headers.

Usually, you begin with name and baseUrl. The name is added to generated query keys so keys from different APIs do not collide.

name
a namespace used in generated query keys.
baseUrl
the base URL for relative endpoint paths.
headers
optional headers added to every request.
fetcher
optional custom fetch function. Default is globalThis.fetch.
onError
optional global error handler for request and auth errors.
TypeScript
1// api/client.ts2import { createMicroApi } from "micro-rq";3 4export const api = createMicroApi({5  name: "main",6  baseUrl: "https://api.example.com",7});

Step 2: Define a resource

Next, group related endpoints into a resource such as users, posts, auth, or orders.

Each endpoint describes what it returns, and when needed, what variables it accepts.

Key points

  • GET endpoints become query endpoints.
  • POST, PUT, PATCH, and DELETE endpoints become mutation endpoints.
  • The first generic is the response type.
  • The second generic is the variables type.
  • If an endpoint has no variables, you can omit the second generic.
TypeScript
1// api/resources/users.ts2import { api } from "../client";3 4export type User = {5  id: string;6  name: string;7  email: string;8};9 10export type CreateUserDto = {11  name: string;12  email: string;13};14 15export const users = api.resource("users", {16  list: api.get<User[]>("/users"),17  detail: api.get<User, string>((id) => `/users/${id}`),18  create: api.post<User, CreateUserDto>("/users"),19});

Step 3: Use a query endpoint

A query endpoint gives you helpers such as toQuery(), key(), baseKey(), and fn().

Most of the time, you will use toQuery() inside useQuery and then add your normal TanStack Query options next to it.

toQuery(variables)
returns { queryKey, queryFn }.
key(variables)
returns the exact key for one request.
baseKey()
returns the stable prefix for broader invalidation.
fn(variables)
runs the request directly.
TypeScript
1import { useQuery } from "@tanstack/react-query";2import { users } from "../api/resources/users";3 4export function UserDetail({ userId }: { userId?: string }) {5  const userQuery = useQuery({6    ...users.detail.toQuery(userId ?? ""),7    enabled: Boolean(userId),8    staleTime: 60_000,9    select: (user) => user.name,10  });11 12  return <div>{userQuery.data}</div>;13}

Step 4: Use a mutation endpoint

A mutation endpoint gives you toMutation() and fn().

Use toMutation() inside useMutation. Then pass variables to mutate or mutateAsync, not to toMutation() itself.

toMutation()
returns { mutationFn }.
fn(variables)
runs the request directly.

Key points

  • Pass mutation variables through TanStack Query's mutate or mutateAsync.
  • Use generated keys for invalidation after success.
TypeScript
1import { useMutation, useQueryClient } from "@tanstack/react-query";2import { users } from "../api/resources/users";3 4export function CreateUserButton() {5  const queryClient = useQueryClient();6 7  const createUser = useMutation({8    ...users.create.toMutation(),9    onSuccess: () => {10      queryClient.invalidateQueries({11        queryKey: users.list.baseKey(),12      });13    },14  });15 16  return (17    <button18      type="button"19      onClick={() => {20        createUser.mutate({21          name: "Jane",22          email: "jane@example.com",23        });24      }}25    >26      Create user27    </button>28  );29}