# routar > Schema-first HTTP API client with end-to-end type safety and runtime validation. Define your API once — reuse it across any transport (fetch, axios, ky), environment (SSR/CSR), and framework. Each endpoint is a Zod-validated async function with fully-inferred types. ## Packages - `@routar/core` — endpoint definitions, router, API client, plugin system, native fetch executor - `@routar/axios` — Axios-based executor - `@routar/ky` — ky-based executor - `@routar/msw` — MSW v2 mock handler factory (for testing) - `@routar/react-query` — TanStack Query bindings; derives typed queryOptions / mutationOptions from your router ## Core API [endpoint(spec)](https://routar.vercel.app/api-reference): Define a single typed endpoint. Enforces path params at compile time. [defineRouter(prefix, endpoints)](https://routar.vercel.app/api-reference): Group endpoints under a shared URL prefix. Supports arbitrary nesting. [createApi(executor, router)](https://routar.vercel.app/api-reference): Build a fully-typed API client. Each endpoint becomes an async function `(params, signal?) => Promise`. [createExecutor(transport, options?)](https://routar.vercel.app/api-reference): Wrap any HTTP client with routar's plugin system. Pass `plugins` via options. [dispatchExecutor(resolver)](https://routar.vercel.app/api-reference): Pick executor at request time — use for SSR/CSR routing without duplicate API instances. ## Executors [createFetchExecutor(baseURL, options?)](https://routar.vercel.app/executors): Native fetch — ideal for SSR with per-request dynamic headers. Built into `@routar/core`. [createAxiosExecutor(instanceOrFactory, options?)](https://routar.vercel.app/executors): Axios instance or async factory — great for CSR with interceptors. From `@routar/axios`. [createKyExecutor(instanceOrFactory, options?)](https://routar.vercel.app/executors): ky instance or factory. From `@routar/ky`. ## Plugins [definePlugin(plugin)](https://routar.vercel.app/api-reference): Create a named plugin with `onRequest`, `onResponse`, `onError` hooks. Use for auth, logging, error normalization. [logger(options?)](https://routar.vercel.app/api-reference): Built-in plugin — logs method, URL, and duration. Pass `{ log }` for a custom log function. `retry` and `timeout` are options on `createFetchExecutor` (not plugins): `retry: 3`, `timeout: 5_000`. ## TanStack Query [createQueries(api, options?)](https://routar.vercel.app/api-reference/create-queries): Derive typed `queryOptions` / `mutationOptions` factories from a routar API client (the router is recovered from the client's `$router` — not re-passed). GET endpoints become query accessors; non-GET endpoints become mutation accessors. From `@routar/react-query`. [routarMutationCache(getClient)](https://routar.vercel.app/api-reference/create-queries): `MutationCache` factory — processes declarative `invalidates` stored in `mutation.meta` after each successful mutation. Wire once at `QueryClient` creation. Without wiring, `invalidates` does nothing (dev `console.warn` emitted). Key helpers: `Query..queryKey(params?)`, `Query..mutationKey`, `Query.$key` (domain root). Prefer narrow invalidation: use specific `queryKey()` over `$key` — `$key` refetches all active queries in the domain. Infinite queries (GET-only): declare the pagination contract once in `createQueries({ infinite: { : { initialPageParam, getNextPageParam, pageParam } } })` — nested routers supported (the map mirrors the router shape). Call `Query..infinite(params?)` with base params only; the contract comes from config. Pass a partial override as the second arg to merge over the config (call wins). If no config exists and no full contract is supplied, a runtime error is thrown. The routar-specific `pageParam` builder `(page) => partialRequest` maps the page param to a partial request (deep-merged into base params) and replaces `queryFn`. Page param type is `number`. Key: `[...root, endpointName, "infinite", params?]` — prefix-child of the standard key, so standard-key invalidation also covers it. Key helper: `Query..infinite.queryKey(params?)`. Per-endpoint defaults: `createQueries(api, { defaults: { getList: { staleTime: 60_000 }, create: { invalidates: [...] } } })` — merged before per-call options (per-call wins); `invalidates` is allowed in mutation defaults. Nested routers supported (the map mirrors the router shape). The second argument may also be a factory `(q) => options` to reference sibling key helpers (e.g. `q.getList.queryKey()`) inside `defaults.invalidates` without circular-variable issues: `createQueries(api, (q) => ({ defaults: { create: { invalidates: [q.getList.queryKey()] } } }))`. Error typing: augment TanStack's `Register` interface (`interface Register { defaultError: HttpError }`) to narrow `error` from `DefaultError` to `HttpError` — no `createQueries` change needed. ## Testing [createMswHandlers(router, baseURL, resolvers)](https://routar.vercel.app): Generate MSW v2 `HttpHandler[]` from a `RouterDef`. Partial mocking supported — only listed endpoints are intercepted. ## Type Utilities [ApiTypes](https://routar.vercel.app/api-reference): Extract `{ request, response }` types from an API client. [PathParams](https://routar.vercel.app/api-reference): Extract `:param` names from a path string as a union type. ## Errors - `ValidationError` — request/response schema mismatch (`@routar/core`) - `HttpError` — non-2xx response from `createFetchExecutor` (has `.status`, `.statusText`, `.body`) - `TimeoutError` — request exceeded the `createFetchExecutor` `timeout` (has `.ms`) ## Full Reference [llms-full.txt](https://routar.vercel.app/llms-full.txt)