Define Routers
On this page
To begin building your tRPC-based API, you'll first need to define your router. Once you've mastered the fundamentals, you can customize your routers for more advanced use cases.
Initialize tRPC
You should initialize tRPC exactly once per application. Multiple instances of tRPC will cause issues: for example, t.mergeRouters() throws if the routers come from different instances that each set a transformer or error formatter. Create t in one file and import your procedures and router from it everywhere.
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';
// You can use any variable name you like.
// We use t to keep things simple.
const const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.create<RuntimeConfigOptions<object, object>>(opts?: RuntimeConfigOptions<object, object> | undefined): TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
export const const router: RouterBuilder<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
router = const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
router;
export const const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure = const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure;You'll notice we are exporting certain methods of the t variable here rather than t itself. This is to establish a certain set of procedures that we will use idiomatically in our codebase.
Defining a router
Next, let's define a router with a procedure to use in our application. We have now created an API "endpoint".
In order for these endpoints to be exposed to the frontend, your Adapter should be configured with your appRouter instance.
import { const publicProcedure: trpc.TRPCProcedureBuilder<object, object, object, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, false, trpc.TRPCDefaultErrorShape>publicProcedure, const router: trpc.TRPCRouterBuilder<{
ctx: object;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
transformer: false;
}>
router } from './trpc';
const const appRouter: trpc.TRPCBuiltRouter<{
ctx: object;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
transformer: false;
}, trpc.TRPCDecorateCreateRouterOptions<{
greeting: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}>>
appRouter = router<{
greeting: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}>(_: {
greeting: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}): trpc.TRPCBuiltRouter<{
ctx: object;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
transformer: false;
}, trpc.TRPCDecorateCreateRouterOptions<{
greeting: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}>>
router({
greeting: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>
greeting: const publicProcedure: trpc.TRPCProcedureBuilder<object, object, object, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, false, trpc.TRPCDefaultErrorShape>publicProcedure.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<object, object, object, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, string>): trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>
query(() => 'hello tRPC v11!'),
});
// Export only the type of a router!
// This prevents us from importing server code on the client.
export type type AppRouter = Router<{
ctx: object;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
transformer: false;
}, trpc.TRPCDecorateCreateRouterOptions<{
greeting: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}>> & trpc.TRPCDecorateCreateRouterOptions<{
greeting: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}>
AppRouter = typeof const appRouter: trpc.TRPCBuiltRouter<{
ctx: object;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
transformer: false;
}, trpc.TRPCDecorateCreateRouterOptions<{
greeting: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}>>
appRouter;On the client, import it with import type { AppRouter } from '...' and never import the appRouter value: a type-only import is always erased, so your server code can't end up in the client bundle.
Procedures can't be named then, call or apply: router() throws if you use these names.
Defining an inline sub-router
When you define an inline sub-router, you can represent your router as a plain object.
In the below example, nested1 and nested2 are equal:
import * as import trpctrpc from '@trpc/server';
import { const publicProcedure: trpc.TRPCProcedureBuilder<object, object, object, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, false, trpc.TRPCDefaultErrorShape>publicProcedure, const router: trpc.TRPCRouterBuilder<{
ctx: object;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
transformer: false;
}>
router } from './trpc';
const const appRouter: trpc.TRPCBuiltRouter<{
ctx: object;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
transformer: false;
}, trpc.TRPCDecorateCreateRouterOptions<{
nested1: trpc.TRPCBuiltRouter<{
ctx: object;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
transformer: false;
}, trpc.TRPCDecorateCreateRouterOptions<{
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}>>;
nested2: {
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
};
}>>
appRouter = router<{
nested1: trpc.TRPCBuiltRouter<{
ctx: object;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
transformer: false;
}, trpc.TRPCDecorateCreateRouterOptions<{
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}>>;
nested2: {
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
};
}>(_: {
nested1: trpc.TRPCBuiltRouter<{
ctx: object;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
transformer: false;
}, trpc.TRPCDecorateCreateRouterOptions<{
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}>>;
nested2: {
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
};
}): trpc.TRPCBuiltRouter<...>
router({
// Using the router() method
nested1: trpc.TRPCBuiltRouter<{
ctx: object;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
transformer: false;
}, trpc.TRPCDecorateCreateRouterOptions<{
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}>>
nested1: router<{
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}>(_: {
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}): trpc.TRPCBuiltRouter<{
ctx: object;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
transformer: false;
}, trpc.TRPCDecorateCreateRouterOptions<{
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}>>
router({
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>
proc: const publicProcedure: trpc.TRPCProcedureBuilder<object, object, object, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, false, trpc.TRPCDefaultErrorShape>publicProcedure.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<object, object, object, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, string>): trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>
query(() => '...'),
}),
// Using an inline sub-router
nested2: {
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>;
}
nested2: {
proc: trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>
proc: const publicProcedure: trpc.TRPCProcedureBuilder<object, object, object, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, false, trpc.TRPCDefaultErrorShape>publicProcedure.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<object, object, object, trpc.TRPCUnsetMarker, trpc.TRPCUnsetMarker, string>): trpc.TRPCQueryProcedure<{
input: void;
output: string;
meta: object;
errorShape: trpc.TRPCDefaultErrorShape;
}>
query(() => '...'),
},
});Advanced usage
When initializing your router, tRPC allows you to:
- Setup request contexts
- Assign metadata to procedures
- Format and handle errors
- Transform data as needed
- Customize the runtime configuration
You can use method chaining to customize your t-object on initialization. For example:
const const t: TRPCRootObject<Context, Meta, {}, {
ctx: Context;
meta: Meta;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.context<Context>(): TRPCBuilder<Context, object>context<type Context = {
userId: string;
}
Context>().TRPCBuilder<Context, object>.meta<Meta>(): TRPCBuilder<Context, Meta>meta<type Meta = {
description: string;
}
Meta>().TRPCBuilder<Context, Meta>.create<{}>(opts?: {} | undefined): TRPCRootObject<Context, Meta, {}, {
ctx: Context;
meta: Meta;
errorShape: DefaultErrorShape;
transformer: false;
}>
create({
/* [...] */
});Runtime Configuration
interface RootConfig {
/**
* Use a data transformer
* @see https://trpc.io/docs/v11/data-transformers
*/
RootConfig.transformer: anytransformer: type DataTransformerOptions = anyDataTransformerOptions;
/**
* Use custom error formatting
* @see https://trpc.io/docs/v11/error-formatting
*/
RootConfig.errorFormatter: anyerrorFormatter: type ErrorFormatter = anyErrorFormatter;
/**
* Allow `@trpc/server` to run in non-server environments
* @warning **Use with caution**, this should likely mainly be used within testing.
* @default false
*/
RootConfig.allowOutsideOfServer: booleanallowOutsideOfServer: boolean;
/**
* Is this a server environment?
* @warning **Use with caution**, this should likely mainly be used within testing.
* @default typeof window === 'undefined' || 'Deno' in window || process.env.NODE_ENV === 'test'
*/
RootConfig.isServer: booleanisServer: boolean;
/**
* Is this development?
* Will be used to decide if the API should return stack traces
* @default process.env.NODE_ENV !== 'production'
*/
RootConfig.isDev: booleanisDev: boolean;
}