Merging Routers
Writing all API-code in your code in the same file is not a great idea. It's easy to merge routers with other routers.
Merging with child routers
server.tsts// @filename: trpc.tsimport {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constmiddleware =t .middleware ;export constrouter =t .router ;export constpublicProcedure =t .procedure ;// @filename: routers/_app.tsimport {router } from '../trpc';import {z } from 'zod';import {userRouter } from './user';import {postRouter } from './post';constappRouter =router ({user :userRouter , // put procedures under "user" namespacepost :postRouter , // put procedures under "post" namespace});export typeAppRouter = typeofappRouter ;// @filename: routers/post.tsimport {router ,publicProcedure } from '../trpc';import {z } from 'zod';export constpostRouter =router ({create :publicProcedure .input (z .object ({title :z .string (),}),).mutation (({input }) => {// [...]}),list :publicProcedure .query (() => {// ...return [];}),});// @filename: routers/user.tsimport {router ,publicProcedure } from '../trpc';import {z } from 'zod';export constuserRouter =router ({list :publicProcedure .query (() => {// [..]return [];}),});
server.tsts// @filename: trpc.tsimport {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constmiddleware =t .middleware ;export constrouter =t .router ;export constpublicProcedure =t .procedure ;// @filename: routers/_app.tsimport {router } from '../trpc';import {z } from 'zod';import {userRouter } from './user';import {postRouter } from './post';constappRouter =router ({user :userRouter , // put procedures under "user" namespacepost :postRouter , // put procedures under "post" namespace});export typeAppRouter = typeofappRouter ;// @filename: routers/post.tsimport {router ,publicProcedure } from '../trpc';import {z } from 'zod';export constpostRouter =router ({create :publicProcedure .input (z .object ({title :z .string (),}),).mutation (({input }) => {// [...]}),list :publicProcedure .query (() => {// ...return [];}),});// @filename: routers/user.tsimport {router ,publicProcedure } from '../trpc';import {z } from 'zod';export constuserRouter =router ({list :publicProcedure .query (() => {// [..]return [];}),});
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:
server/_app.tstsimport * astrpc from '@trpc/server';import {publicProcedure ,router } from './trpc';constappRouter =router ({// Shorthand plain object for creating a sub-routernested1 : {proc :publicProcedure .query (() => '...'),},//nested2 :router ({proc :publicProcedure .query (() => '...'),}),});
server/_app.tstsimport * astrpc from '@trpc/server';import {publicProcedure ,router } from './trpc';constappRouter =router ({// Shorthand plain object for creating a sub-routernested1 : {proc :publicProcedure .query (() => '...'),},//nested2 :router ({proc :publicProcedure .query (() => '...'),}),});
We recommend you to only define inline sub-routers within a file, and to keep the exported routers as a t.router object. This makes any potential type errors show up in the file they originate from, and not at the place where you merge them.
See a deep dive here
When defining a router as a plain object, any keys are valid. This means you can define a router like this:
routers/user.tstsexport constuserRouter = {nested : {notAProcedure : () => 'Hello world', // <-- actual error here},};
routers/user.tstsexport constuserRouter = {nested : {notAProcedure : () => 'Hello world', // <-- actual error here},};
without any errors being shown. But when you try to merge this router somewhere else, things will blow up:
routers/user.tstsexport constappRouter =router ({Type '{ nested: { notAProcedure: () => string; }; }' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '{ nested: { notAProcedure: () => string; }; }' is not assignable to type 'ProcedureRouterRecord'. Property 'nested' is incompatible with index signature. Type '{ notAProcedure: () => string; }' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '{ notAProcedure: () => string; }' is not assignable to type 'ProcedureRouterRecord'. Property 'notAProcedure' is incompatible with index signature. Type '() => string' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '() => string' is missing the following properties from type 'Procedure<"query" | "mutation" | "subscription", any>': _type, _def, _procedure2322Type '{ nested: { notAProcedure: () => string; }; }' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '{ nested: { notAProcedure: () => string; }; }' is not assignable to type 'ProcedureRouterRecord'. Property 'nested' is incompatible with index signature. Type '{ notAProcedure: () => string; }' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '{ notAProcedure: () => string; }' is not assignable to type 'ProcedureRouterRecord'. Property 'notAProcedure' is incompatible with index signature. Type '() => string' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '() => string' is missing the following properties from type 'Procedure<"query" | "mutation" | "subscription", any>': _type, _def, _procedure: user userRouter , // <-- ❌ error displayed here});
routers/user.tstsexport constappRouter =router ({Type '{ nested: { notAProcedure: () => string; }; }' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '{ nested: { notAProcedure: () => string; }; }' is not assignable to type 'ProcedureRouterRecord'. Property 'nested' is incompatible with index signature. Type '{ notAProcedure: () => string; }' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '{ notAProcedure: () => string; }' is not assignable to type 'ProcedureRouterRecord'. Property 'notAProcedure' is incompatible with index signature. Type '() => string' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '() => string' is missing the following properties from type 'Procedure<"query" | "mutation" | "subscription", any>': _type, _def, _procedure2322Type '{ nested: { notAProcedure: () => string; }; }' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '{ nested: { notAProcedure: () => string; }; }' is not assignable to type 'ProcedureRouterRecord'. Property 'nested' is incompatible with index signature. Type '{ notAProcedure: () => string; }' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '{ notAProcedure: () => string; }' is not assignable to type 'ProcedureRouterRecord'. Property 'notAProcedure' is incompatible with index signature. Type '() => string' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '() => string' is missing the following properties from type 'Procedure<"query" | "mutation" | "subscription", any>': _type, _def, _procedure, // <-- ❌ error displayed here user :userRouter });
This can be very confusing, and if your routers are big with lots of procedures, the error message will be impossible to comprehend. To fix this, only use inline sub-routers within a file, and keep the exported routers as a t.router object. This way, the error will show up in the file where the error originates from, and not at the place where you merge them:
routers/user.tstsexport constuserRouter =router ({nested : {Type '() => string' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '() => string' is missing the following properties from type 'Procedure<"query" | "mutation" | "subscription", any>': _type, _def, _procedure2322Type '() => string' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '() => string' is missing the following properties from type 'Procedure<"query" | "mutation" | "subscription", any>': _type, _def, _procedure: () => 'Hello world', // <-- ✅ error displayed where it originates notAProcedure },});
routers/user.tstsexport constuserRouter =router ({nested : {Type '() => string' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '() => string' is missing the following properties from type 'Procedure<"query" | "mutation" | "subscription", any>': _type, _def, _procedure2322Type '() => string' is not assignable to type 'ProcedureRouterRecord | AnyProcedure | AnyRouter'. Type '() => string' is missing the following properties from type 'Procedure<"query" | "mutation" | "subscription", any>': _type, _def, _procedure: () => 'Hello world', // <-- ✅ error displayed where it originates notAProcedure },});
routers/_app.tstsexport constappRouter =router ({user :userRouter ,});
routers/_app.tstsexport constappRouter =router ({user :userRouter ,});
Merging with t.mergeRouters
If you prefer having all procedures flat in one single namespace, you can instead use t.mergeRouters
server.tsts// @filename: routers/_app.tsimport {router ,publicProcedure ,mergeRouters } from '../trpc';import {z } from 'zod';import {userRouter } from './user';import {postRouter } from './post';constappRouter =mergeRouters (userRouter ,postRouter )export typeAppRouter = typeofappRouter ;// @filename: routers/post.tsimport {router ,publicProcedure } from '../trpc';import {z } from 'zod';export constpostRouter =router ({postCreate :publicProcedure .input (z .object ({title :z .string (),}),).mutation (({input }) => {// [...]}),postList :publicProcedure .query (() => {// ...return [];}),});// @filename: routers/user.tsimport {router ,publicProcedure } from '../trpc';import {z } from 'zod';export constuserRouter =router ({userList :publicProcedure .query (() => {// [..]return [];}),});
server.tsts// @filename: routers/_app.tsimport {router ,publicProcedure ,mergeRouters } from '../trpc';import {z } from 'zod';import {userRouter } from './user';import {postRouter } from './post';constappRouter =mergeRouters (userRouter ,postRouter )export typeAppRouter = typeofappRouter ;// @filename: routers/post.tsimport {router ,publicProcedure } from '../trpc';import {z } from 'zod';export constpostRouter =router ({postCreate :publicProcedure .input (z .object ({title :z .string (),}),).mutation (({input }) => {// [...]}),postList :publicProcedure .query (() => {// ...return [];}),});// @filename: routers/user.tsimport {router ,publicProcedure } from '../trpc';import {z } from 'zod';export constuserRouter =router ({userList :publicProcedure .query (() => {// [..]return [];}),});