endpoint(spec)
Defines a single endpoint. The primary purpose of this helper is to provide full type inference on adapter without manual casts — raw is automatically typed from the response schema.
import { endpoint } from '@routar/core'
import { z } from 'zod'
const TodoSchema = z.object({ id: z.number(), title: z.string() })
// without adapter
endpoint({
method: 'GET',
path: '/',
response: z.array(TodoSchema),
})
// with adapter — raw is inferred as z.infer<typeof TodoSchema>[]
endpoint({
method: 'GET',
path: '/',
response: z.array(TodoSchema),
adapter: (raw) => raw.map(todo => ({ ...todo, label: todo.title })),
})Parameters
| Field | Type | Required | Description |
|---|---|---|---|
method | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | Yes | HTTP method |
path | string | Yes | Path relative to the router prefix. Use :param for path parameters. |
response | Validator<T> | Yes | Schema for validating the response. Any object with .parse(data): T. |
request | Validator<R> | No | Schema for { path?, query?, body? }. |
adapter | (raw: T) => U | No | Transform the validated response. |
Path param enforcement
If path contains :param segments, the request schema must include a matching path object — enforced at compile time.
// ✅ correct
endpoint({
path: '/:id',
request: z.object({ path: z.object({ id: z.number() }) }),
response: TodoSchema,
method: 'GET',
})
// ❌ compile error — ':id' in path but request.path.id missing
endpoint({
path: '/:id',
request: z.object({ query: z.object({ q: z.string() }) }),
response: TodoSchema,
method: 'GET',
})Adapter
response and adapter are always separate. response is a pure Zod schema (stays composable). adapter is a plain function that transforms the validated data.
// ✅ adapter separately — schema stays composable
const TodoRawSchema = z.object({ id: z.number(), title: z.string() })
const toTodoItem = (raw: z.infer<typeof TodoRawSchema>) => ({
...raw,
label: raw.title,
})
endpoint({
method: 'GET',
path: '/',
response: z.array(TodoRawSchema),
adapter: (raw) => raw.map(toTodoItem), // raw is correctly typed
})Never use .transform() on a response schema. .transform() wraps the schema in a ZodEffects object, which loses .extend(), .merge(), and all other composition methods — your schema can no longer be reused or composed. Put data transformations in adapter instead; the response schema stays a plain ZodObject.
Type utilities
ApiTypes<TApi>
Extracts request and response types from an API client. See the dedicated ApiTypes<T> reference for full usage and patterns.
import type { ApiTypes } from '@routar/core'
type TodoApiTypes = ApiTypes<typeof todoApi>
type CreateRequest = TodoApiTypes['create']['request'] // { body: { title: string } }
type CreateResponse = TodoApiTypes['create']['response'] // TodoPathParams<TPath>
Extracts :param names from a path string as a union type.
import type { PathParams } from '@routar/core'
type P = PathParams<'/:userId/posts/:postId'> // 'userId' | 'postId'