From harness-claude
Organizes tRPC procedures into nested routers for multi-domain APIs like users and posts, merging them into a single appRouter while preserving end-to-end type safety. Use for splitting large APIs across files.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Organize type-safe RPC procedures into nested routers that merge into a single appRouter
Builds end-to-end type-safe tRPC APIs with routers, procedures, middleware, subscriptions, and Next.js/React integration for TypeScript full-stack apps.
Guides tRPC end-to-end typesafe API development with router architecture, procedure design, Zod input validation, middleware chaining via unstable_pipe, TRPCError handling, and Vertical Slice pattern.
Integrates tRPC with Next.js App Router via fetch adapter API routes, client providers for React components, and server callers for React Server Components without HTTP round-trips.
Share bugs, ideas, or general feedback.
Organize type-safe RPC procedures into nested routers that merge into a single appRouter
server/trpc.ts — export t, router, publicProcedure, and protectedProcedure from this file.server/routers/ — each exports a router created with createTRPCRouter().appRouter in server/root.ts using createTRPCRouter({ ... }) with each sub-router as a property.AppRouter type from server/root.ts — import it in the client to infer all procedure types.initTRPC instances — share the single t export across all router files.usersRouter, postsRouter, commentsRouter — the property name becomes the namespace in client calls.// server/trpc.ts — single tRPC initialization
import { initTRPC } from '@trpc/server';
import type { TRPCContext } from './context';
const t = initTRPC.context<TRPCContext>().create({
transformer: superjson,
});
export const router = t.router;
export const publicProcedure = t.procedure;
export const middleware = t.middleware;
// server/routers/posts.ts — domain router
import { z } from 'zod';
import { router, publicProcedure, protectedProcedure } from '../trpc';
export const postsRouter = router({
list: publicProcedure
.input(z.object({ limit: z.number().min(1).max(100).default(20) }))
.query(({ ctx, input }) => ctx.db.post.findMany({ take: input.limit })),
create: protectedProcedure
.input(z.object({ title: z.string().min(1), content: z.string() }))
.mutation(({ ctx, input }) =>
ctx.db.post.create({ data: { ...input, authorId: ctx.session.user.id } })
),
});
// server/root.ts — merge all routers
import { router } from './trpc';
import { postsRouter } from './routers/posts';
import { usersRouter } from './routers/users';
export const appRouter = router({
posts: postsRouter,
users: usersRouter,
});
export type AppRouter = typeof appRouter;
tRPC uses TypeScript's structural type system to infer the entire API contract from the router definition — no code generation, no schema files, no runtime reflection. The AppRouter type is the single source of truth for both client and server.
Namespace hierarchy: Nesting routers ({ posts: postsRouter }) creates a namespace. Client calls become api.posts.list.useQuery(). This mirrors the router file structure and makes API organization visible to consumers.
Single initTRPC instance: initTRPC is called once and the resulting t object is shared. Calling it multiple times produces isolated tRPC instances that cannot be merged — a common mistake when trying to split initialization.
router() vs mergeRouters(): createTRPCRouter({ posts: postsRouter }) is namespace-based composition — the sub-router's procedures are nested under the key. mergeRouters(routerA, routerB) is flat composition — all procedures from both routers appear at the same level. Use namespacing for domain separation; use mergeRouters sparingly for cross-cutting procedures.
superjson transformer: tRPC procedures serialize/deserialize inputs and outputs as JSON by default. Adding superjson as a transformer enables passing Date, Map, Set, BigInt, and other non-JSON types through procedures. Add it to both initTRPC on the server and the client link configuration.
Export only AppRouter type, not the instance: The appRouter instance contains server-only dependencies (db client, etc.). Export only typeof appRouter (the type) for client consumption — never import the server router directly in client code.
https://trpc.io/docs/server/routers