---
title: Type Helpers
description: Infer route types for clients, TanStack Query, and server handlers
---

Type helpers take a route declaration and infer the type used at a specific
runtime boundary.

```ts
import { route, router, webSocketMessages } from "@rest-rpc/core";
import { z } from "zod";

export const api = router({
	todos: {
		get: route({
			method: "GET",
			path: "/todos/:id",
			pathParams: z.object({ id: z.string() }),
			query: z.object({ includeDone: z.boolean().optional() }),
			responses: {
				200: z.object({ id: z.string(), title: z.string() }),
				404: z.object({ code: z.literal("NOT_FOUND") }),
			},
		}),
		page: route({
			method: "GET",
			path: "/todos/page",
			query: z.object({
				cursor: z.string().optional(),
				status: z.enum(["open", "done"]),
				limit: z.number(),
			}),
			responses: {
				200: z.object({
					items: z.array(z.object({ id: z.string(), title: z.string() })),
					nextCursor: z.string().optional(),
				}),
			},
		}),
		create: route({
			method: "POST",
			path: "/todos",
			body: z.object({ title: z.string() }),
			response: z.object({ id: z.string(), title: z.string() }),
		}),
		watch: route({
			mode: "webSocket",
			path: "/todos/:id/watch",
			pathParams: z.object({ id: z.string() }),
			messages: {
				client: webSocketMessages("action", {
					typing: z.object({ active: z.boolean() }),
					rename: z.object({ title: z.string() }),
				}),
				server: z.object({ title: z.string() }),
			},
		}),
	},
});
```

## Fetch Client

Import fetch client helpers from `@rest-rpc/core`.

```ts
import type {
	ClientReceived,
	ClientRequest,
	ClientResponse,
	ClientResponseBody,
	ClientSent,
	ClientSocket,
} from "@rest-rpc/core";

type GetTodoRequest = ClientRequest<typeof api.todos.get>;
// { id: string; includeDone?: boolean }

type GetTodoResponse = ClientResponse<typeof api.todos.get>;
// | { declared: true; status: 200; body: { id: string; title: string }; headers: Headers }
// | { declared: true; status: 404; body: { code: "NOT_FOUND" }; headers: Headers }
// | { declared: false; status: number; body: unknown; headers: Headers }

type GetTodoBody = ClientResponseBody<typeof api.todos.get>;
// { id: string; title: string }

type WatchSocket = ClientSocket<typeof api.todos.watch>;
// {
//   send(message: ClientSent<typeof api.todos.watch>): void;
//   onMessage(callback: (message: ClientReceived<typeof api.todos.watch>) => void): () => void;
// }

type WatchSent = ClientSent<typeof api.todos.watch>;
// | { action: "typing"; message: { active: boolean } }
// | { action: "rename"; message: { title: string } }

type WatchReceived = ClientReceived<typeof api.todos.watch>;
// { title: string }
```

`ClientResponse` is the return value of `fetchResponse()`.
`ClientResponseBody` is the return value of `fetch()` for routes with one
successful response body.

## TanStack Query

Import TanStack Query helpers from `@rest-rpc/tanstack-query`.

```ts
import type {
	RouteInfiniteQueryData,
	RouteMutationVariables,
	RouteQueryData,
	RouteQueryError,
} from "@rest-rpc/tanstack-query";

type GetTodoData = RouteQueryData<typeof api.todos.get>;
// { status: 200; body: { id: string; title: string }; headers: Headers }

type TodoPagesData = RouteInfiniteQueryData<typeof api.todos.page>;
// {
//   pages: Array<{
//     status: 200;
//     body: {
//       items: Array<{ id: string; title: string }>;
//       nextCursor?: string;
//     };
//     headers: Headers;
//   }>;
//   pageParams: Array<{
//     cursor?: string;
//     status: "open" | "done";
//     limit: number;
//   }>;
// }

type GetTodoError = RouteQueryError<typeof api.todos.get>;
// | { status: 404; body: { code: "NOT_FOUND" }; headers: Headers }
// | { declared: false; status: number; body: unknown; headers: Headers }
// | Error

type CreateTodoVariables = RouteMutationVariables<typeof api.todos.create>;
// { title: string }
```

## Server

Import server helpers from the server adapter you use. The adapter-specific
`RouteRequest` includes that adapter's handler context.

```ts
import type {
	RouteErrors,
	RouteReceived,
	RouteRequest,
	RouteRequestData,
	RouteResponse,
	RouteResponseShorthand,
	RouteSent,
	RouteSocket,
} from "@rest-rpc/express";

type GetTodoRequest = RouteRequest<typeof api.todos.get>;
// { id: string; includeDone?: boolean; context: HttpRouteHandlerContext }

type GetTodoRequestData = RouteRequestData<typeof api.todos.get>;
// { id: string; includeDone?: boolean }

type GetTodoResponse = RouteResponse<typeof api.todos.get>;
// | { status: 200; body: { id: string; title: string } }
// | { status: 404; body: { code: "NOT_FOUND" } }

type GetTodoErrors = RouteErrors<typeof api.todos.get>;
// { status: 404; body: { code: "NOT_FOUND" } }

type CreateTodoShorthand = RouteResponseShorthand<typeof api.todos.create>;
// { id: string; title: string }

type WatchSocket = RouteSocket<typeof api.todos.watch>;
// {
//   send(message: RouteSent<typeof api.todos.watch>): void;
//   onMessage(callback: (message: RouteReceived<typeof api.todos.watch>) => void | Promise<void>): () => void;
// }

type WatchSent = RouteSent<typeof api.todos.watch>;
// { title: string }

type WatchReceived = RouteReceived<typeof api.todos.watch>;
// | { action: "typing"; message: { active: boolean } }
// | { action: "rename"; message: { title: string } }
```

Use `RouteResponse` when returning an explicit status and body. Use
`RouteResponseShorthand` when returning the successful response body directly.
