Contract Route Builder
Declare your routes separately from their handlers.
Import route from @rest-rpc/core to declare a route independently
of its server implementation. The contract can be shared directly with a Fetch
client and implemented with any server adapter.
The declaration methods are the same as the Route Builder. The difference is that outputs are declared with schemas rather than inferred from handlers.
Declare Routes
import { route } from "@rest-rpc/core";
import { z } from "zod";
const todoSchema = z.object({ id: z.string(), title: z.string() });
const create = route.input(z.object({ title: z.string() })).output(todoSchema);
const get = route
.get("/todos/:id")
.params(z.object({ id: z.string() }))
.response(200, todoSchema)
.response(404, z.object({ code: z.literal("TODO_NOT_FOUND") }));
export const api = { todos: { create, get } };
Use .output() for a plain result or .response() for status responses.
Streams use .streamOutput() or .streamResponse(). These schemas define
handler and client types and provide runtime validation.
Implement Routes
Each server adapter exports implement(). Pass a route or route tree to get
builders with typed .handler() methods:
import { implement } from "@rest-rpc/express";
import { api } from "./contract";
import { createTodo, findTodo } from "./todos";
const builders = implement(api);
const create = builders.todos.create.handler(({ input: { title } }) =>
createTodo(title),
);
const get = builders.todos.get.handler(({ params }) => {
const todo = findTodo(params.id);
return todo
? { status: 200, body: todo }
: { status: 404, body: { code: "TODO_NOT_FOUND" } };
});
export const routes = { todos: { create, get } };
The implementation must match the contract’s inputs and declared outputs. Register the completed routes with your adapter as usual.
Use in a Client
Pass the shared contract directly to initClient():
import { initClient } from "@rest-rpc/core";
import { api } from "./contract";
const client = initClient(api, { baseUrl: "https://api.example.com" });
See the Contract-first Quickstart for a complete example and Fetch Client for client options.