Schemas
Use Standard Schema-compatible libraries or type-only contracts.
The contract accepts Standard Schema-compatible schemas.
Zod, Valibot, and ArkType are common choices. Other Standard Schema-compatible libraries can be used when the request key shape is known or provided.
How schemas are used
rest-rpc uses schemas for request validation and response validation.
At runtime:
- Server validates request input and returns schema output to a handler.
- Server validates handler output and returns schema output to the client.
- Client can optionally enable response validation to validate schema output from the server.
For typescript:
- Fetch client request input is schema input.
- Fetch client response output is schema output.
- Server handler input is schema output.
- Server handler output is schema input.
For further details, see HTTP and Schema I/O gotchas.
Type-Only Schemas
Use build-in type<T>() when the contract should carry TypeScript types but runtime
validation is unnecessary or handled elsewhere.
import { router, type } from "@rest-rpc/core";
export const api = router({
todos: {
get: {
method: "GET",
path: "/todos/:id",
request: {
params: {
id: type<string>(),
},
},
responses: {
200: type<{
id: string;
title: string;
completed: boolean;
}>(),
},
},
},
});
Request Key Inference
Flattened handler and client input requires rest-rpc to know the request keys.
The library includes built-in request key inference for common object schemas from Zod, Valibot, and ArkType.
request: {
params: z.object({
id: z.string(),
}),
query: z.object({
includeCompleted: z.boolean().optional(),
}),
}
When the key shape is not inferable, use a record-shaped request declaration.
request: {
query: {
includeCompleted: type<boolean | undefined>(),
},
}
As a last resort, provide requestKeys.
request: {
body: type<{
title: string;
}>(),
requestKeys: {
title: "body",
},
}
Most routes should not need manual request keys.
Request Key Constraints
Flattened keys must be unambiguous.
This route is invalid:
request: {
params: {
id: z.string(),
},
body: {
id: z.string(),
},
}
Both fields would flatten to id.
Important constraints:
- Duplicate flattened keys across
body,query,params, andheadersare invalid. contextis reserved for server handler context.- Header keys are checked case-insensitively for duplicates.
content-typeis reserved as a request header.- Path params in the URL must have matching
paramsschema keys. paramsschema keys must correspond to actual path params.- When using
customBody(), the flattenedbodykey is reserved for the custom body value.
Request Value Constraints
Request body fields can be normal JSON-like values.
Query values, params, and headers are serialized into HTTP fields, so their runtime values should be scalar request values (string, number, boolean).
await api.todos.search.fetch({
query: "docs",
limit: 20,
includeCompleted: false,
"x-request-id": "req_1",
});
Nested values usually belong in the JSON body or in a custom body.