Skip to content
rest-rpc
Esc
↑↓navigate↵open⌘Jpreview

REST APIs with RPC-like ergonomics.

Define REST APIs with methods, paths, request segments, response statuses, content types, and streaming—or start with sensible defaults. Call them through a typed client that feels like ordinary function calls.

rest-rpc puts HTTP first. Define routes alongside their handlers and infer client types from their return values. Use your existing server framework, middleware, and deployment model.

Use Only the Details You Need

// POST /todos/create, flat JSON input, plain result.
const 
const create: HandlerImplementation<{
    readonly kind: "procedure";
    readonly method: "POST";
} & {
    readonly path: "";
} & {
    request: EmptyObject & {
        body: z.ZodObject<{
            title: z.ZodString;
        }, z.core.$strip>;
        contentType: "application/json";
    };
} & {
    responses: EmptyObject;
} & {
    readonly input: "input";
} & EmptyObject, ExpressHandlerFields, DefaultContext, {
    id: string;
    title: string;
}, Omit<{
    readonly kind: "procedure";
    readonly method: "POST";
} & {
    readonly path: "";
} & {
    request: EmptyObject & {
        body: z.ZodObject<{
            title: z.ZodString;
        }, z.core.$strip>;
        contentType: "application/json";
    };
} & {
    responses: EmptyObject;
} & {
    readonly input: "input";
} & EmptyObject, "responses"> & {
    ...;
} & {
    ...;
}>
create
= const route: ServerRouteBuilder<ExpressHandlerFields, DefaultContext>
Entry point for declaring routes with handlers for Express.
@remarksResponses are inferred from the handler unless they are declared before `.handler()`.@see{@link https://rest-rpc.dev/docs/quickstart#define-and-register-routes}@see{@link https://rest-rpc.dev/docs/server/express#framework-context}
route
.
input<z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>, undefined, "", never>(this: BuilderReceiver<"", never>, schema: z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>, options?: undefined): RouteBuilderView<WithFlatInput<DerivedState, z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>, undefined>, ServerBuilderExtension<ExpressHandlerFields, DefaultContext>, "", never>
Declares one flat input value. GET inputs use query encoding; other methods use a body.
input
(import zz.
function object<{
    title: z.ZodString;
}>(shape?: {
    title: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>
object
({ title: z.ZodStringtitle: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.
handler<{
    id: string;
    title: string;
}>(handler: HandlerFor<{
    readonly kind: "procedure";
    readonly method: "POST";
} & {
    readonly path: "";
} & {
    request: EmptyObject & {
        body: z.ZodObject<{
            title: z.ZodString;
        }, z.core.$strip>;
        contentType: "application/json";
    };
} & {
    responses: EmptyObject;
} & {
    readonly input: "input";
} & EmptyObject, ExpressHandlerFields, DefaultContext, {
    id: string;
    title: string;
}>): HandlerImplementation<{
    readonly kind: "procedure";
    readonly method: "POST";
} & ... 4 more ... & EmptyObject, ExpressHandlerFields, DefaultContext, {
    id: string;
    title: string;
}, Omit<...> & ... 1 more ... & {
    ...;
}>
handler
(({
input: {
    title: string;
}
input
: { title: stringtitle } }) =>
function createTodo(title: string): {
    id: string;
    title: string;
}
createTodo
(title: stringtitle));
// Explicit method, path, request segments, statuses, and response headers. const
const get: HandlerImplementation<{
    readonly kind: "http";
    readonly method: "GET";
} & {
    readonly path: "/todos/:id";
} & {
    request: Omit<Omit<Omit<EmptyObject, "params"> & Record<"params", z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>, "query"> & Record<"query", z.ZodObject<{
        uppercase: z.ZodOptional<z.ZodEnum<{
            yes: "yes";
            no: "no";
        }>>;
    }, z.core.$strip>>, "headers"> & Record<"headers", z.ZodObject<{
        "x-request-id": z.ZodOptional<z.ZodString>;
    }, z.core.$strip>>;
} & {
    ...;
} & {
    ...;
} & EmptyObject, ExpressHandlerFields, DefaultContext, {
    ...;
} | {
    ...;
}, Omit<...> & ... 1 more ... & {
    ...;
}>
get
= const route: ServerRouteBuilder<ExpressHandlerFields, DefaultContext>
Entry point for declaring routes with handlers for Express.
@remarksResponses are inferred from the handler unless they are declared before `.handler()`.@see{@link https://rest-rpc.dev/docs/quickstart#define-and-register-routes}@see{@link https://rest-rpc.dev/docs/server/express#framework-context}
route
.get<"/todos/:id">(path: "/todos/:id"): RouteBuilderView<HttpStateFor<"GET">, ServerBuilderExtension<ExpressHandlerFields, DefaultContext>, "/todos/:id", never>get("/todos/:id") .
params<z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>, "/todos/:id", never>(this: BuilderReceiver<"/todos/:id", never>, schema: z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>): RouteBuilderView<WithRequest<HttpStateFor<"GET">, "params", z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>, "params">, ServerBuilderExtension<ExpressHandlerFields, DefaultContext>, "/todos/:id", never>
Declares path parameters.
@see{@link https://rest-rpc.dev/docs/route-builder#declare-request-segments}
params
(import zz.
function object<{
    id: z.ZodString;
}>(shape?: {
    id: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>
object
({ id: z.ZodStringid: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.
query<z.ZodObject<{
    uppercase: z.ZodOptional<z.ZodEnum<{
        yes: "yes";
        no: "no";
    }>>;
}, z.core.$strip>, "/todos/:id", never>(this: BuilderReceiver<"/todos/:id", never>, schema: z.ZodObject<{
    uppercase: z.ZodOptional<z.ZodEnum<{
        yes: "yes";
        no: "no";
    }>>;
}, z.core.$strip>): RouteBuilderView<WithRequest<WithRequest<HttpStateFor<"GET">, "params", z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>, "params">, "query", z.ZodObject<{
    uppercase: z.ZodOptional<z.ZodEnum<{
        yes: "yes";
        no: "no";
    }>>;
}, z.core.$strip>, "query">, ServerBuilderExtension<...>, "/todos/:id", never>
Declares URL query parameters.
@see{@link https://rest-rpc.dev/docs/route-builder#declare-request-segments}
query
(import zz.
function object<{
    uppercase: z.ZodOptional<z.ZodEnum<{
        yes: "yes";
        no: "no";
    }>>;
}>(shape?: {
    uppercase: z.ZodOptional<z.ZodEnum<{
        yes: "yes";
        no: "no";
    }>>;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    uppercase: z.ZodOptional<z.ZodEnum<{
        yes: "yes";
        no: "no";
    }>>;
}, z.core.$strip>
object
({
uppercase: z.ZodOptional<z.ZodEnum<{
    yes: "yes";
    no: "no";
}>>
uppercase
: import zz.
enum<readonly ["yes", "no"]>(values: readonly ["yes", "no"], params?: string | z.core.$ZodEnumParams): z.ZodEnum<{
    yes: "yes";
    no: "no";
}> (+1 overload)
export enum
enum
(["yes", "no"]).
ZodType<any, any, $ZodEnumInternals<{ yes: "yes"; no: "no"; }>>.optional(): z.ZodOptional<z.ZodEnum<{
    yes: "yes";
    no: "no";
}>>
optional
() }))
.
headers<z.ZodObject<{
    "x-request-id": z.ZodOptional<z.ZodString>;
}, z.core.$strip>, "/todos/:id", never>(this: BuilderReceiver<"/todos/:id", never>, schema: z.ZodObject<{
    "x-request-id": z.ZodOptional<z.ZodString>;
}, z.core.$strip>): RouteBuilderView<WithRequest<WithRequest<WithRequest<HttpStateFor<"GET">, "params", z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>, "params">, "query", z.ZodObject<{
    uppercase: z.ZodOptional<z.ZodEnum<{
        yes: "yes";
        no: "no";
    }>>;
}, z.core.$strip>, "query">, "headers", z.ZodObject<...>, "headers">, ServerBuilderExtension<...>, "/todos/:id", never>
Declares request headers.
@see{@link https://rest-rpc.dev/docs/route-builder#declare-request-segments}
headers
(import zz.
function object<{
    "x-request-id": z.ZodOptional<z.ZodString>;
}>(shape?: {
    "x-request-id": z.ZodOptional<z.ZodString>;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    "x-request-id": z.ZodOptional<z.ZodString>;
}, z.core.$strip>
object
({ "x-request-id": import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string().ZodType<any, any, $ZodStringInternals<string>>.optional(): z.ZodOptional<z.ZodString>optional() }))
.
handler<{
    readonly status: 404;
    readonly body: {
        readonly code: "TODO_NOT_FOUND";
    };
    readonly responseHeaders?: undefined;
} | {
    readonly status: 200;
    readonly responseHeaders: {
        readonly "x-request-id": string | undefined;
    };
    readonly body: {
        readonly title: string;
        readonly id: string;
        readonly code?: undefined;
    };
}>(handler: HandlerFor<{
    readonly kind: "http";
    readonly method: "GET";
} & {
    readonly path: "/todos/:id";
} & {
    request: Omit<Omit<Omit<EmptyObject, "params"> & Record<"params", z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>, "query"> & Record<...>, "headers"> & Record<...>;
} & {
    ...;
} & {
    ...;
} & EmptyObject, ExpressHandlerFields, DefaultContext, {
    readonly status: 404;
    readonly body: {
        readonly code: "TODO_NOT_FOUND";
    };
    readonly responseHeaders?: undefined;
} | {
    readonly status: 200;
    readonly responseHeaders: {
        readonly "x-request-id": string | undefined;
    };
    readonly body: {
        readonly title: string;
        readonly id: string;
        readonly code?: undefined;
    };
}>): HandlerImplementation<...>
handler
(({
params: {
    id: string;
}
params
,
query: {
    uppercase?: "yes" | "no" | undefined;
}
query
,
headers: {
    "x-request-id"?: string | undefined;
}
headers
}) => {
const
const todo: {
    id: string;
    title: string;
} | undefined
todo
=
function findTodo(id: string): {
    id: string;
    title: string;
} | undefined
findTodo
(
params: {
    id: string;
}
params
.id: stringid);
if (!
const todo: {
    id: string;
    title: string;
} | undefined
todo
) {
return { status: 404status: 404,
body: {
    readonly code: "TODO_NOT_FOUND";
}
body
: { code: "TODO_NOT_FOUND"code: "TODO_NOT_FOUND" as type const = "TODO_NOT_FOUND"const } };
} return { status: 200status: 200,
responseHeaders: {
    readonly "x-request-id": string | undefined;
}
responseHeaders
: { "x-request-id":
headers: {
    "x-request-id"?: string | undefined;
}
headers
["x-request-id"] },
body: {
    readonly title: string;
    readonly id: string;
    readonly code?: undefined;
}
body
: {
...
const todo: {
    id: string;
    title: string;
}
todo
,
title: stringtitle:
query: {
    uppercase?: "yes" | "no" | undefined;
}
query
.uppercase?: "yes" | "no" | undefineduppercase === "yes" ?
const todo: {
    id: string;
    title: string;
}
todo
.title: stringtitle.String.toUpperCase(): string
Converts all the alphabetic characters in a string to uppercase.
toUpperCase
() :
const todo: {
    id: string;
    title: string;
}
todo
.title: stringtitle,
}, }; }); export const
const routes: {
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: z.ZodObject<{
                    title: z.ZodString;
                }, z.core.$strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: z.ZodObject<{
                    title: z.ZodString;
                }, z.core.$strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}
routes
= {
todos: {
    create: HandlerImplementation<{
        readonly kind: "procedure";
        readonly method: "POST";
    } & {
        readonly path: "";
    } & {
        request: EmptyObject & {
            body: z.ZodObject<{
                title: z.ZodString;
            }, z.core.$strip>;
            contentType: "application/json";
        };
    } & {
        responses: EmptyObject;
    } & {
        readonly input: "input";
    } & EmptyObject, ExpressHandlerFields, DefaultContext, {
        id: string;
        title: string;
    }, Omit<{
        readonly kind: "procedure";
        readonly method: "POST";
    } & {
        readonly path: "";
    } & {
        request: EmptyObject & {
            body: z.ZodObject<{
                title: z.ZodString;
            }, z.core.$strip>;
            contentType: "application/json";
        };
    } & {
        responses: EmptyObject;
    } & {
        readonly input: "input";
    } & EmptyObject, "responses"> & {
        ...;
    } & {
        ...;
    }>;
    get: HandlerImplementation<...>;
}
todos
: {
create: HandlerImplementation<{
    readonly kind: "procedure";
    readonly method: "POST";
} & {
    readonly path: "";
} & {
    request: EmptyObject & {
        body: z.ZodObject<{
            title: z.ZodString;
        }, z.core.$strip>;
        contentType: "application/json";
    };
} & {
    responses: EmptyObject;
} & {
    readonly input: "input";
} & EmptyObject, ExpressHandlerFields, DefaultContext, {
    id: string;
    title: string;
}, Omit<{
    readonly kind: "procedure";
    readonly method: "POST";
} & {
    readonly path: "";
} & {
    request: EmptyObject & {
        body: z.ZodObject<{
            title: z.ZodString;
        }, z.core.$strip>;
        contentType: "application/json";
    };
} & {
    responses: EmptyObject;
} & {
    readonly input: "input";
} & EmptyObject, "responses"> & {
    ...;
} & {
    ...;
}>
create
,
get: HandlerImplementation<{
    readonly kind: "http";
    readonly method: "GET";
} & {
    readonly path: "/todos/:id";
} & {
    request: Omit<Omit<Omit<EmptyObject, "params"> & Record<"params", z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>, "query"> & Record<"query", z.ZodObject<{
        uppercase: z.ZodOptional<z.ZodEnum<{
            yes: "yes";
            no: "no";
        }>>;
    }, z.core.$strip>>, "headers"> & Record<"headers", z.ZodObject<{
        "x-request-id": z.ZodOptional<z.ZodString>;
    }, z.core.$strip>>;
} & {
    ...;
} & {
    ...;
} & EmptyObject, ExpressHandlerFields, DefaultContext, {
    ...;
} | {
    ...;
}, Omit<...> & ... 1 more ... & {
    ...;
}>
get
} };
import { function initClient<TContract extends Contract, const TGlobalHeaders extends Record<string, string> = Record<never, string>>(contract: TContract, options: ApiClientOptions<TGlobalHeaders>): ApiClientFor<TContract, TGlobalHeaders>
Creates a typed fetch client whose shape mirrors a contract.
@see{@link https://rest-rpc.dev/docs/client/fetch-client}
initClient
} from "@rest-rpc/core";
import { function generateContractFromType<TContract extends Contract>(options: generateContractFromTypeOptions): TContract
Generates a minimal JSON-compatible contract from an exported server route tree type.
@remarksThe generation uses TypeScript compiler API to extract only the minimal required information from an exported server route tree type to produce a minimal JSON-compatible contract that can be used to create an API client. Provide the exported route tree type as the generic argument to preserve the full type information when passing the result to `initClient`.
generateContractFromType
} from "@rest-rpc/core/generate";
import type {
const routes: {
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}
routes
} from "./server";
// Run generation at build time for browser clients. const
const api: {
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}
api
=
generateContractFromType<{
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}>(options: generateContractFromTypeOptions): {
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}
Generates a minimal JSON-compatible contract from an exported server route tree type.
@remarksThe generation uses TypeScript compiler API to extract only the minimal required information from an exported server route tree type to produce a minimal JSON-compatible contract that can be used to create an API client. Provide the exported route tree type as the generic argument to preserve the full type information when passing the result to `initClient`.
generateContractFromType
<typeof
const routes: {
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}
routes
>({
filePath: string
Path to the TypeScript source file that exports the server route tree type.
filePath
: "./server.ts",
exportName: string
Name of the exported server route tree to generate a contract from.
exportName
: "routes",
}); const
const client: {
    todos: {
        create: FetchResponseFn<Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        } & {
            ...;
        }, Record<...>>;
        get: FetchResponseFn<...>;
    };
}
client
=
initClient<{
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}, Record<...>>(contract: {
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}, options: ApiClientOptions<...>): {
    ...;
}
Creates a typed fetch client whose shape mirrors a contract.
@see{@link https://rest-rpc.dev/docs/client/fetch-client}
initClient
(
const api: {
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}
api
, { baseUrl: stringbaseUrl: "http://localhost:3000" });
const
const todo: {
    id: string;
    title: string;
}
todo
= await
const client: {
    todos: {
        create: FetchResponseFn<Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        } & {
            ...;
        }, Record<...>>;
        get: FetchResponseFn<...>;
    };
}
client
.
todos: {
    create: FetchResponseFn<Omit<{
        readonly kind: "procedure";
        readonly method: "POST";
    } & {
        readonly path: "";
    } & {
        request: EmptyObject & {
            body: ZodObject<{
                title: ZodString;
            }, $strip>;
            contentType: "application/json";
        };
    } & {
        responses: EmptyObject;
    } & {
        readonly input: "input";
    } & EmptyObject, "responses"> & {
        ...;
    } & {
        ...;
    } & {
        ...;
    }, Record<...>>;
    get: FetchResponseFn<...>;
}
todos
.
create: (request: {
    title: string;
}, options?: FetchOptionsFor<Omit<{
    readonly kind: "procedure";
    readonly method: "POST";
} & {
    readonly path: "";
} & {
    request: EmptyObject & {
        body: ZodObject<{
            title: ZodString;
        }, $strip>;
        contentType: "application/json";
    };
} & {
    responses: EmptyObject;
} & {
    readonly input: "input";
} & EmptyObject, "responses"> & {
    responses: {
        200: {
            body: ClientSchema<{
                id: string;
                title: string;
            }>;
            contentType: "application/json";
        };
    };
} & {
    output: "output";
} & {
    readonly handler: HandlerFor<...>;
}> | undefined) => Promise<...>
create
({ title: stringtitle: "Write docs" });
const
const response: {
    status: 404;
    body: {
        readonly code: "TODO_NOT_FOUND";
    };
    headers: Headers;
} | {
    status: 200;
    body: {
        readonly title: string;
        readonly id: string;
        readonly code?: undefined;
    };
    responseHeaders: SerializedResponseHeaders<{
        readonly "x-request-id": string | undefined;
    }>;
    headers: Headers;
}
response
= await
const client: {
    todos: {
        create: FetchResponseFn<Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        } & {
            ...;
        }, Record<...>>;
        get: FetchResponseFn<...>;
    };
}
client
.
todos: {
    create: FetchResponseFn<Omit<{
        readonly kind: "procedure";
        readonly method: "POST";
    } & {
        readonly path: "";
    } & {
        request: EmptyObject & {
            body: ZodObject<{
                title: ZodString;
            }, $strip>;
            contentType: "application/json";
        };
    } & {
        responses: EmptyObject;
    } & {
        readonly input: "input";
    } & EmptyObject, "responses"> & {
        ...;
    } & {
        ...;
    } & {
        ...;
    }, Record<...>>;
    get: FetchResponseFn<...>;
}
todos
.
get: (request: {
    query: {
        uppercase?: "yes" | "no" | undefined;
    };
    params: {
        id: string;
    };
    headers?: {
        "x-request-id"?: string | undefined;
    } | undefined;
}, options?: FetchOptionsFor<Omit<{
    readonly kind: "http";
    readonly method: "GET";
} & {
    readonly path: "/todos/:id";
} & {
    request: Omit<Omit<Omit<EmptyObject, "params"> & Record<"params", ZodObject<{
        id: ZodString;
    }, $strip>>, "query"> & Record<"query", ZodObject<{
        uppercase: ZodOptional<ZodEnum<{
            yes: "yes";
            no: "no";
        }>>;
    }, $strip>>, "headers"> & Record<...>;
} & {
    ...;
} & {
    ...;
} & EmptyObject, "responses"> & {
    ...;
} & {
    ...;
} & {
    ...;
}> | undefined) => Promise<...>
get
({
params: {
    id: string;
}
params
: { id: stringid:
const todo: {
    id: string;
    title: string;
}
todo
.id: stringid },
query: {
    uppercase?: "yes" | "no" | undefined;
}
query
: { uppercase?: "yes" | "no" | undefineduppercase: "yes" },
headers?: {
    "x-request-id"?: string | undefined;
} | undefined
headers
: { "x-request-id": "req_1" },
}); if (
const response: {
    status: 404;
    body: {
        readonly code: "TODO_NOT_FOUND";
    };
    headers: Headers;
} | {
    status: 200;
    body: {
        readonly title: string;
        readonly id: string;
        readonly code?: undefined;
    };
    responseHeaders: SerializedResponseHeaders<{
        readonly "x-request-id": string | undefined;
    }>;
    headers: Headers;
}
response
.status: 404 | 200status === 200) {
var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
(
const response: {
    status: 200;
    body: {
        readonly title: string;
        readonly id: string;
        readonly code?: undefined;
    };
    responseHeaders: SerializedResponseHeaders<{
        readonly "x-request-id": string | undefined;
    }>;
    headers: Headers;
}
response
.
body: {
    readonly title: string;
    readonly id: string;
    readonly code?: undefined;
}
body
.title: stringtitle);
}
import { function createTanstackQueryHelpers<TContract extends Contract, const TGlobalHeaders extends Record<string, string> = Record<never, string>>(contract: TContract, options: CreateTanstackQueryHelpersOptions<TGlobalHeaders>): TanstackQueryHelpersFor<TContract>
Creates a TanStack Query helper tree that mirrors a contract.
@remarksGenerated helpers create options and stable query keys; they do not create a `QueryClient` or call framework-specific hooks.@see{@link https://rest-rpc.dev/docs/client/tanstack-query#setup}
createTanstackQueryHelpers
} from "@rest-rpc/tanstack-query";
import { function useQuery<TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = readonly unknown[]>(options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): DefinedUseQueryResult<NoInfer<TData>, TError> (+2 overloads)useQuery, function useMutation<TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown>(options: UseMutationOptions<TData, TError, TVariables, TOnMutateResult>, queryClient?: QueryClient): UseMutationResult<TData, TError, TVariables, TOnMutateResult>useMutation } from "@tanstack/react-query"; const
const tq: TanstackQueryTreeFor<{
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}>
tq
=
createTanstackQueryHelpers<{
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}, Record<...>>(contract: {
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}, options: CreateTanstackQueryHelpersOptions<...>): TanstackQueryTreeFor<...>
Creates a TanStack Query helper tree that mirrors a contract.
@remarksGenerated helpers create options and stable query keys; they do not create a `QueryClient` or call framework-specific hooks.@see{@link https://rest-rpc.dev/docs/client/tanstack-query#setup}
createTanstackQueryHelpers
(
const api: {
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}
api
, {
baseUrl: stringbaseUrl: "http://localhost:3000", }); const
const todo: UseQueryResult<NoInfer<{
    status: 200;
    body: {
        readonly title: string;
        readonly id: string;
        readonly code?: undefined;
    };
    responseHeaders: SerializedResponseHeaders<{
        readonly "x-request-id": string | undefined;
    }>;
    headers: Headers;
}>, DeclaredRouteQueryError<Omit<{
    readonly kind: "http";
    readonly method: "GET";
} & {
    readonly path: "/todos/:id";
} & {
    request: Omit<Omit<Omit<EmptyObject, "params"> & Record<"params", ZodObject<{
        id: ZodString;
    }, $strip>>, "query"> & Record<"query", ZodObject<...>>, "headers"> & Record<...>;
} & {
    ...;
} & {
    ...;
} & EmptyObject, "responses"> & {
    ...;
} & {
    ...;
} & {
    ...;
}>>
todo
=
useQuery<{
    status: 200;
    body: {
        readonly title: string;
        readonly id: string;
        readonly code?: undefined;
    };
    responseHeaders: SerializedResponseHeaders<{
        readonly "x-request-id": string | undefined;
    }>;
    headers: Headers;
}, DeclaredRouteQueryError<Omit<{
    readonly kind: "http";
    readonly method: "GET";
} & {
    readonly path: "/todos/:id";
} & {
    request: Omit<Omit<Omit<EmptyObject, "params"> & Record<"params", ZodObject<{
        id: ZodString;
    }, $strip>>, "query"> & Record<"query", ZodObject<{
        uppercase: ZodOptional<ZodEnum<{
            ...;
        }>>;
    }, $strip>>, "headers"> & Record<...>;
} & {
    ...;
} & {
    ...;
} & EmptyObject, "responses"> & {
    ...;
} & {
    ...;
} & {
    ...;
}>, {
    status: 200;
    body: {
        readonly title: string;
        readonly id: string;
        readonly code?: undefined;
    };
    responseHeaders: SerializedResponseHeaders<{
        readonly "x-request-id": string | undefined;
    }>;
    headers: Headers;
}, readonly unknown[]>(options: UndefinedInitialDataOptions<...>, queryClient?: QueryClient): UseQueryResult<...> (+2 overloads)
useQuery
(
const tq: TanstackQueryTreeFor<{
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}>
tq
.
todos: TanstackQueryTreeFor<{
    create: HandlerImplementation<{
        readonly kind: "procedure";
        readonly method: "POST";
    } & {
        readonly path: "";
    } & {
        request: EmptyObject & {
            body: ZodObject<{
                title: ZodString;
            }, $strip>;
            contentType: "application/json";
        };
    } & {
        responses: EmptyObject;
    } & {
        readonly input: "input";
    } & EmptyObject, ExpressHandlerFields, DefaultContext, {
        id: string;
        title: string;
    }, Omit<{
        readonly kind: "procedure";
        readonly method: "POST";
    } & {
        readonly path: "";
    } & {
        request: EmptyObject & {
            body: ZodObject<{
                title: ZodString;
            }, $strip>;
            contentType: "application/json";
        };
    } & {
        responses: EmptyObject;
    } & {
        readonly input: "input";
    } & EmptyObject, "responses"> & {
        ...;
    } & {
        ...;
    }>;
    get: HandlerImplementation<...>;
}>
todos
.
get: TanstackQueryRouteValue<HandlerImplementation<{
    readonly kind: "http";
    readonly method: "GET";
} & {
    readonly path: "/todos/:id";
} & {
    request: Omit<Omit<Omit<EmptyObject, "params"> & Record<"params", ZodObject<{
        id: ZodString;
    }, $strip>>, "query"> & Record<"query", ZodObject<...>>, "headers"> & Record<...>;
} & {
    ...;
} & {
    ...;
} & EmptyObject, ExpressHandlerFields, DefaultContext, {
    ...;
} | {
    ...;
}, Omit<...> & ... 1 more ... & {
    ...;
}>>
get
.
queryOptions: <{
    status: 200;
    body: {
        readonly title: string;
        readonly id: string;
        readonly code?: undefined;
    };
    responseHeaders: SerializedResponseHeaders<{
        readonly "x-request-id": string | undefined;
    }>;
    headers: Headers;
}>(request: {
    query: {
        uppercase?: "yes" | "no" | undefined;
    };
    params: {
        id: string;
    };
    headers?: {
        "x-request-id"?: string | undefined;
    } | undefined;
} | unique symbol, options?: QueryOptionsFor<HandlerImplementation<{
    readonly kind: "http";
    readonly method: "GET";
} & {
    readonly path: "/todos/:id";
} & {
    request: Omit<Omit<Omit<EmptyObject, "params"> & Record<...>, "query"> & Record<...>, "headers"> & Record<...>;
} & {
    ...;
} & {
    ...;
} & EmptyObject, ExpressHandlerFields, DefaultContext, {
    ...;
} | {
    ...;
}, Omit<...> & ... 1 more ... & {
    ...;
}>, {
    status: 200;
    body: {
        readonly title: string;
        readonly id: string;
        readonly code?: undefined;
    };
    responseHeaders: SerializedResponseHeaders<{
        readonly "x-request-id": string | undefined;
    }>;
    headers: Headers;
}> | undefined) => QueryOptionsResultFor<...>
queryOptions
({
params: {
    id: string;
}
params
: { id: stringid: "todo_1" },
query: {
    uppercase?: "yes" | "no" | undefined;
}
query
: { uppercase?: "yes" | "no" | undefineduppercase: "yes" },
}), ); const
const create: UseMutationResult<{
    id: string;
    title: string;
}, Error | HttpError, {
    title: string;
}, unknown>
create
=
useMutation<{
    id: string;
    title: string;
}, Error | HttpError, {
    title: string;
}, unknown>(options: UseMutationOptions<{
    id: string;
    title: string;
}, Error | HttpError, {
    title: string;
}, unknown>, queryClient?: QueryClient): UseMutationResult<{
    id: string;
    title: string;
}, Error | HttpError, {
    title: string;
}, unknown>
useMutation
(
const tq: TanstackQueryTreeFor<{
    todos: {
        create: HandlerImplementation<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, ExpressHandlerFields, DefaultContext, {
            id: string;
            title: string;
        }, Omit<{
            readonly kind: "procedure";
            readonly method: "POST";
        } & {
            readonly path: "";
        } & {
            request: EmptyObject & {
                body: ZodObject<{
                    title: ZodString;
                }, $strip>;
                contentType: "application/json";
            };
        } & {
            responses: EmptyObject;
        } & {
            readonly input: "input";
        } & EmptyObject, "responses"> & {
            ...;
        } & {
            ...;
        }>;
        get: HandlerImplementation<...>;
    };
}>
tq
.
todos: TanstackQueryTreeFor<{
    create: HandlerImplementation<{
        readonly kind: "procedure";
        readonly method: "POST";
    } & {
        readonly path: "";
    } & {
        request: EmptyObject & {
            body: ZodObject<{
                title: ZodString;
            }, $strip>;
            contentType: "application/json";
        };
    } & {
        responses: EmptyObject;
    } & {
        readonly input: "input";
    } & EmptyObject, ExpressHandlerFields, DefaultContext, {
        id: string;
        title: string;
    }, Omit<{
        readonly kind: "procedure";
        readonly method: "POST";
    } & {
        readonly path: "";
    } & {
        request: EmptyObject & {
            body: ZodObject<{
                title: ZodString;
            }, $strip>;
            contentType: "application/json";
        };
    } & {
        responses: EmptyObject;
    } & {
        readonly input: "input";
    } & EmptyObject, "responses"> & {
        ...;
    } & {
        ...;
    }>;
    get: HandlerImplementation<...>;
}>
todos
.
create: TanstackQueryRouteValue<HandlerImplementation<{
    readonly kind: "procedure";
    readonly method: "POST";
} & {
    readonly path: "";
} & {
    request: EmptyObject & {
        body: ZodObject<{
            title: ZodString;
        }, $strip>;
        contentType: "application/json";
    };
} & {
    responses: EmptyObject;
} & {
    readonly input: "input";
} & EmptyObject, ExpressHandlerFields, DefaultContext, {
    id: string;
    title: string;
}, Omit<{
    readonly kind: "procedure";
    readonly method: "POST";
} & {
    readonly path: "";
} & {
    request: EmptyObject & {
        body: ZodObject<{
            title: ZodString;
        }, $strip>;
        contentType: "application/json";
    };
} & {
    responses: EmptyObject;
} & {
    readonly input: "input";
} & EmptyObject, "responses"> & {
    ...;
} & {
    ...;
}>>
create
.
mutationOptions: (options?: MutationOptionsFor<HandlerImplementation<{
    readonly kind: "procedure";
    readonly method: "POST";
} & {
    readonly path: "";
} & {
    request: EmptyObject & {
        body: ZodObject<{
            title: ZodString;
        }, $strip>;
        contentType: "application/json";
    };
} & {
    responses: EmptyObject;
} & {
    readonly input: "input";
} & EmptyObject, ExpressHandlerFields, DefaultContext, {
    id: string;
    title: string;
}, Omit<{
    readonly kind: "procedure";
    readonly method: "POST";
} & {
    readonly path: "";
} & {
    request: EmptyObject & {
        body: ZodObject<{
            title: ZodString;
        }, $strip>;
        contentType: "application/json";
    };
} & {
    responses: EmptyObject;
} & {
    readonly input: "input";
} & EmptyObject, "responses"> & {
    ...;
} & {
    ...;
}>> | undefined) => MutationOptions<...>
mutationOptions
());
const create: UseMutationResult<{
    id: string;
    title: string;
}, Error | HttpError, {
    title: string;
}, unknown>
create
.
mutate: (variables: {
    title: string;
}, options?: MutateOptions<{
    id: string;
    title: string;
}, Error | HttpError, {
    title: string;
}, unknown> | undefined) => void
The mutation function you can call with variables to trigger the mutation and optionally hooks on additional callback options.
@paramvariables - The variables object to pass to the `mutationFn`.@paramoptions.onSuccess - This function will fire when the mutation is successful and will be passed the mutation's result.@paramoptions.onError - This function will fire if the mutation encounters an error and will be passed the error.@paramoptions.onSettled - This function will fire when the mutation is either successfully fetched or encounters an error and be passed either the data or error.@remarks- If you make multiple requests, `onSuccess` will fire only after the latest call you've made. - All the callback functions (`onSuccess`, `onError`, `onSettled`) are void functions, and the returned value will be ignored.
mutate
({ title: stringtitle: "Write docs" });
curl http://localhost:3000/todos/create \
	-H 'content-type: application/json' \
	-d '{"title":"Write docs"}'

curl 'http://localhost:3000/todos/todo_1?uppercase=yes' \
	-H 'x-request-id: req_1'

Plain outputs reach the client as data. Returns with status reach it as HTTP response envelopes. The builder keeps both shapes typed without making you choose a separate route kind.

npx skills add rest-rpc/rest-rpc

What You Get

REST APIs

Build methods, paths, request locations, response bodies, and metadata with a fluent API that keeps invalid combinations out of route declarations.

Optional shared contracts

Declare an HTTP interface separately when it should be shared independently of the server implementation.

Composable route builders

Choose a method and path, flat input or request segments, and plain output or status responses independently in one fluent builder.

RPC-like ergonomics

Call routes and implement handlers as ordinary functions without compromising HTTP semantics. Your server works exactly like it did before, just with full type safety at the API boundary.

Server adapters

Use Express, Hono, Fastify, NestJS, Node HTTP, or fetch-native runtimes with your existing middleware and application code.

Typed responses

Return direct success bodies for simple routes, or status-keyed response envelopes when the route has multiple expected outcomes.

Schema options

Use Standard Schema-compatible libraries such as Zod, Valibot, or ArkType, or built-in type-only helper when runtime validation is not needed.

Generated helpers

Derive typed fetch clients, TanStack Query options, query keys, and OpenAPI documents from the same route tree.

Beyond basic JSON

Use typed streams, custom bodies, response headers, and content types in the same route builder.

What’s Next

Build the first route

Start with the Quickstart if you want the fastest end-to-end example.

Shape the routes

Use the Route Builder to choose the HTTP details your API needs.

Choose the runtime

Describe the API

Generate an OpenAPI document without ceremony.

Common Questions

Who is rest-rpc for?

rest-rpc is for developers who want end-to-end type safety and RPC-like ergonomics without replacing familiar HTTP concepts. You keep methods, paths, headers, bodies, status codes, and your existing framework, while route declarations connect server and client types.

Do I have to restructure my app?

No. Keep your framework, middleware, authentication, database access, and deployment model. Add typed routes at the API boundary and adopt the client, TanStack Query helpers, or OpenAPI generation as they become useful.

Do I need to learn a new API model?

The model is ordinary HTTP: methods, paths, request locations, status codes, headers, and response bodies. Choose explicit details when you need them or use defaults for simple routes.

Can I have a separate contract?

Yes. Declare the HTTP interface independently, implement it with any server adapter, and share it directly with clients. See the Contract-first Quickstart.

What happens when something breaks?

You debug the same boundary you would debug by hand: method, path, query, headers, body, status, and response shape. Because those pieces live in one route declaration, type errors and validation errors point back to that declaration.

Was this page helpful?