Documentation
Errors
Handle failed responses, missing auth, response parsing, and global error observation.
Errors
Failed HTTP responses throw MicroApiError.
Missing required auth throws MicroAuthRequiredError before fetch is called.
Network errors, refresh errors, and JSON parsing errors are thrown as their original errors.
- MicroApiError.status
- response status number.
- MicroApiError.statusText
- response status text.
- MicroApiError.data
- parsed error body, text, or
null. - MicroApiError.response
- original
Responseobject.
1import { MicroApiError, MicroAuthRequiredError } from "micro-rq";2 3try {4 await users.detail.fn("user-1")();5} catch (error) {6 if (error instanceof MicroApiError) {7 console.log(error.status, error.data);8 }9 10 if (error instanceof MicroAuthRequiredError) {11 console.log("The user must sign in first.");12 }13}Global error observer
onError lets the API client observe request failures without replacing the original error.
Use it for logging, telemetry, or auth cleanup. TanStack Query still receives the original thrown error.
1const api = createMicroApi({2 name: "main",3 baseUrl: "/api",4 onError: (error, context) => {5 console.log(context.method, context.url, error);6 },7});Handling 401 Unauthorized
For endpoints with authMode: "optional" or authMode: "required", a 401 automatically triggers tokenProvider.refreshAccessToken() when refresh is configured.
micro-rq retries the original request once after refresh succeeds. If the retry still returns 401, the final MicroApiError is thrown to TanStack Query and observed by onError.
Use global onError for auth cleanup after the final failure. Do not clear tokens on the first 401; micro-rq may still be able to refresh and retry.
- Auto refresh does not run when the endpoint uses `authMode
- "none"`.
Key points
- Auto refresh happens only for
401. - The request is retried once, not forever.
- After the final failure, TanStack Query receives the original
MicroApiError.
1const api = createMicroApi({2 name: "main",3 baseUrl: "/api",4 tokenProvider,5 authHeader: (token) => ({6 Authorization: `Bearer ${token}`,7 }),8 onError: (error, context) => {9 if (error instanceof MicroApiError && error.status === 401) {10 // This is the final 401 after refresh/retry failed.11 localStorage.removeItem("accessToken");12 localStorage.removeItem("refreshToken");13 14 if (context.authMode === "required") {15 router.navigate("/login");16 }17 }18 },19});1const meQuery = useQuery({2 ...auth.me.toQuery(),3 retry: false,4 onError: (error) => {5 if (error instanceof MicroApiError && error.status === 401) {6 toast.error("Your session expired. Please sign in again.");7 }8 },9});Handling 403 Forbidden
403 means the request was authenticated, but the user is not allowed to perform that action.
micro-rq does not refresh the token for 403. Refreshing usually will not help because the access token is valid but lacks permission.
Handle 403 as an authorization or permissions problem: show an access-denied state, redirect to a safe page, or hide unavailable actions.
- 401
- authentication problem; refresh may help.
- 403
- authorization problem; refresh normally should not run.
Key points
- Use TanStack Query
onErrorfor page-specific UI behavior. - Use global
onErrorfor telemetry or central permission logging.
1const deleteUser = useMutation({2 ...users.remove.toMutation(),3 onError: (error) => {4 if (error instanceof MicroApiError && error.status === 403) {5 toast.error("You do not have permission to delete this user.");6 }7 },8});Forcing a token refresh
Automatic refresh is tied to 401 responses. If you need to refresh before a request, call tokenProvider.refreshAccessToken() manually.
This is useful before a sensitive action, before opening a long-lived screen, or after the app becomes active again.
After manual refresh succeeds, invalidate or refetch the queries that should use the new access token.
Key points
- Manual refresh uses the same shared refresh promise, so parallel calls still share one refresh request.
- Manual refresh throws if refresh is not configured or refresh fails.
- Use
queryClient.invalidateQueries()after refresh when existing cached data should be reloaded.
1async function refreshSession() {2 await tokenProvider.refreshAccessToken();3 4 await queryClient.invalidateQueries({5 queryKey: auth.me.baseKey(),6 });7}1const saveSettings = useMutation({2 mutationFn: async (values: SettingsDto) => {3 await tokenProvider.refreshAccessToken();4 5 return settings.update.fn(values);6 },7});Response parsing
Successful responses are parsed before they are returned from queryFn, mutationFn, or fn().
Key points
- 204 responses return
undefined. - Empty successful bodies return
undefined. - JSON content types are parsed with
JSON.parse. - Non-JSON bodies return text.
- Empty error bodies become
nullonMicroApiError.data.