Routers
Plain objects of procedures, merged with mergeRouters and split with lazy().
On this page
A router is a plain object. Keys are path segments: { post: { byId } } serves post.byId, at /trpc/post.byId over HTTP.
export const const appRouter: {
health: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: string;
errors: never;
services: never;
}>;
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}
appRouter = {
health: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: string;
errors: never;
services: never;
}>
health: const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure.ProcedureBuilder<RootDef<Context, {}>>.query<string>(resolver: (opts: ResolverOpts<RootDef<Context, {}>>) => string): Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: string;
errors: never;
services: never;
}>
query(() => 'ok'),
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
}
post: { byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>
byId, create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<ZodObject<{
title: ZodString;
}, $strip>>;
services: never;
}>
create },
};Merge routers with mergeRouters(a, b). A key in both is a type error and a runtime error; object spread would silently keep the last.
With thousands of procedures, split the router into packages and type apps from the server's built declarations: see Large routers.
Lazy subtrees
lazy() loads a subtree on its first call, for large routers that should start fast (serverless cold starts). The client types it as if it were inline.
import { function lazy<TRouter extends AnyRouter>(load: () => Promise<TRouter | {
default: TRouter;
}>): LazyRouter<TRouter>
lazy } from '@trpc12/trpc/server';
export const const appRouter: {
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
admin: LazyRouter<...>;
}
appRouter = {
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
}
post: const postRouter: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
}
postRouter,
admin: LazyRouter<{
stats: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: number;
errors: never;
services: never;
}>;
}>
admin: lazy<{
stats: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: number;
errors: never;
services: never;
}>;
}>(load: () => Promise<{
stats: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: number;
errors: never;
services: never;
}>;
} | {
default: {
stats: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: number;
errors: never;
services: never;
}>;
};
}>): LazyRouter<{
stats: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: number;
errors: never;
services: never;
}>;
}>
lazy(() => import('./admin.ts')),
};t.lazy(load, { meta }) also takes router-level meta, as t.router() does.
load may return the router or a module whose default export is the router. Each subtree loads once.
- The RPC handlers,
createRouterClientandlocalLinkload it on the first call under it. - Tools that need the whole router ask for it loaded:
await loadRouter(appRouter).