tRPC
Pre-configured tRPC v11 with end-to-end typesafe API routes, context creation, and optional TanStack Query integration.
What create-faster adds
Files added:
src/trpc/
├── init.ts # createTRPCContext() + procedure definitions
├── client.ts # Standalone tRPC client with httpBatchLink
├── server.tsx # Server-side utilities (RSC caller or Query proxy)
├── query-client.ts # QueryClient factory (if TanStack Query)
├── providers.tsx # TRPCReactProvider (if TanStack Query)
└── routers/
├── _app.ts # Root router combining all routers
└── hello.ts # Example router with Zod validation
src/app/api/trpc/
└── [trpc]/route.ts # Fetch adapter API handler
# Turborepo only:
packages/api/
├── src/
│ ├── index.ts # Public exports
│ ├── trpc.ts # Context + procedures
│ ├── root.ts # Root router
│ └── router/
│ └── hello.ts # Example router
├── package.json # @repo/api
└── tsconfig.jsoninit.ts
Context creation and procedure definitions. Automatically includes db if ORM is selected, and session + protectedProcedure if Better Auth is installed:
import { db } from '@/lib/db';
import { initTRPC, TRPCError } from '@trpc/server';
import superjson from 'superjson';
export async function createTRPCContext() {
return {
db,
};
}
type TRPCContext = Awaited<ReturnType<typeof createTRPCContext>>;
const t = initTRPC.context<TRPCContext>().create({
transformer: superjson,
});
export const router = t.router;
export const publicProcedure = t.procedure;With Better Auth, the context includes session and a protectedProcedure middleware is exported:
export const protectedProcedure = publicProcedure.use(async (opts) => {
if (!opts.ctx.session?.user) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'You must be logged in to access this resource',
});
}
return opts.next({
ctx: {
...opts.ctx,
session: opts.ctx.session,
user: opts.ctx.session.user,
},
});
});routers/hello.ts
Example router with Zod validation:
import { z } from 'zod';
import { publicProcedure, router } from '@/trpc/init';
export const helloRouter = router({
greet: publicProcedure
.input(z.object({ name: z.string().min(1) }))
.query(({ input }) => {
return { greeting: `Hello, ${input.name}!` };
}),
});routers/_app.ts
Root router combining all routers with type exports:
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import { router } from '@/trpc/init';
import { helloRouter } from './hello';
export const appRouter = router({
hello: helloRouter,
});
export type AppRouter = typeof appRouter;
export type RouterInput = inferRouterInputs<AppRouter>;
export type RouterOutput = inferRouterOutputs<AppRouter>;server.tsx
Server-side utilities for RSC. Behavior depends on whether TanStack Query is selected:
With TanStack Query — provides createTRPCOptionsProxy for RSC queries with hydration:
import 'server-only';
import type React from 'react';
import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
import { createTRPCOptionsProxy, type TRPCQueryOptions } from '@trpc/tanstack-react-query';
import { cache } from 'react';
import { makeQueryClient } from '@/trpc/query-client';
import { appRouter } from '@/trpc/routers/_app';
import { createTRPCContext } from '@/trpc/init';
const getQueryClient = cache(makeQueryClient);
export const trpc = createTRPCOptionsProxy({
ctx: createTRPCContext,
router: appRouter,
queryClient: getQueryClient,
});
export function HydrateClient(props: { children: React.ReactNode }) {
const queryClient = getQueryClient();
const state = dehydrate(queryClient);
return <HydrationBoundary state={state}>{props.children}</HydrationBoundary>;
}
export function prefetch<T extends ReturnType<TRPCQueryOptions<any>>>(queryOptions: T) {
const queryClient = getQueryClient();
if (queryOptions.queryKey[1]?.type === 'infinite') {
void queryClient.prefetchInfiniteQuery(queryOptions as any);
} else {
void queryClient.prefetchQuery(queryOptions);
}
}Without TanStack Query — provides a direct caller for server-side procedure calls:
import 'server-only';
import { appRouter } from '@/trpc/routers/_app';
import { createTRPCContext } from '@/trpc/init';
export const trpc = appRouter.createCaller(createTRPCContext);Turborepo mode:
Creates @repo/api package depending on @repo/db and @repo/auth. App imports from @repo/api. All @/trpc imports become @repo/api imports.

