Skip to content

tRPC v12 isn’t on npm yet. The @trpc12/* packages on this site can’t be installed until it’s released. Use v11 today

tRPC

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, createRouterClient and localLink load it on the first call under it.
  • Tools that need the whole router ask for it loaded: await loadRouter(appRouter).