Define your API schema once — get end-to-end type safety and runtime validation across any HTTP client and any environment.
npm install @routar/coreimport { z } from 'zod'
import { endpoint, defineRouter, createApi, createFetchExecutor } from '@routar/core'
const TodoSchema = z.object({ id: z.number(), title: z.string(), done: z.boolean() })
const api = createApi(createFetchExecutor('https://api.example.com'), defineRouter('/todos', {
list: endpoint({ method: 'GET', path: '/', response: z.array(TodoSchema) }),
detail: endpoint({ method: 'GET', path: '/:id', response: TodoSchema,
request: { path: z.object({ id: z.number() }) } }),
create: endpoint({ method: 'POST', path: '/', response: TodoSchema,
request: { body: z.object({ title: z.string() }) } }),
}))
const todos = await api.list() // Todo[]
const todo = await api.detail({ path: { id: 1 } }) // Todo
const next = await api.create({ body: { title: 'buy milk' } }) // TodoThe contrast
AI can write all 6 files. But when the API changes, it has to find and update every one — or ship drift.
Composable by design
Swap executors, stack plugins, combine routers, bind TanStack Query. Every control is one architecture layer — the types assemble themselves.
@routar/react-query
Most wrappers generate a useTodos() hook — callable only inside a component, one way. routar derives queryOptions and mutationOptions straight from your router: plain objects you reuse anywhere.
import { useQuery, useSuspenseQuery, useMutation } from '@tanstack/react-query'
import { createQueries } from '@routar/react-query'
// Keys + queryFn + mutationFn — all derived from `api`. One source of truth.
export const todoQuery = createQueries(todoApi)
// It returns options objects, not hooks — reuse them anywhere:
useQuery(todoQuery.getList()) // client component
useSuspenseQuery(todoQuery.getDetail({ path: { id: 1 } }))
queryClient.prefetchQuery(todoQuery.getList()) // server / RSC
queryClient.ensureQueryData(todoQuery.getDetail({ path: { id: 1 } }))
// Compose & override per call — they're just objects
useQuery({ ...todoQuery.getList(), staleTime: 60_000 })
// Mutations are options too
useMutation(todoQuery.create()) // POST → mutationOptionsPackages
@routar/corenpm install @routar/core@routar/axiosnpm install @routar/axios@routar/kynpm install @routar/ky@routar/mswnpm install @routar/msw@routar/react-querynpm install @routar/react-query