packages/server/skills/validators/SKILL.md
// server/trpc.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
export const router = t.router;
export const publicProcedure = t.procedure;
// server/appRouter.ts
import { z } from 'zod';
import { publicProcedure, router } from './trpc';
export const appRouter = router({
hello: publicProcedure
.input(z.object({ name: z.string() }))
.output(z.object({ greeting: z.string() }))
.query(({ input }) => {
return { greeting: `hello ${input.name}` };
}),
});
export type AppRouter = typeof appRouter;
import { z } from 'zod';
import { publicProcedure, router } from './trpc';
export const appRouter = router({
userById: publicProcedure.input(z.string()).query(({ input }) => {
return { id: input, name: 'Katt' };
}),
userCreate: publicProcedure
.input(z.object({ name: z.string(), email: z.string().email() }))
.mutation(({ input }) => {
return { id: '1', ...input };
}),
});
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const baseProcedure = t.procedure
.input(z.object({ townName: z.string() }))
.use((opts) => {
console.log(`Request from: ${opts.input.townName}`);
return opts.next();
});
export const appRouter = t.router({
hello: baseProcedure
.input(z.object({ name: z.string() }))
.query(({ input }) => {
return { greeting: `Hello ${input.name}, from ${input.townName}` };
}),
});
Multiple .input() calls merge object properties; the final input type is { townName: string; name: string }.
import { z } from 'zod';
import { publicProcedure, router } from './trpc';
export const appRouter = router({
hello: publicProcedure
.output(z.object({ greeting: z.string() }))
.query(() => {
return { greeting: 'hello world' };
}),
});
Output validation catches mismatches between your return type and the expected shape, useful for untrusted data sources.
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
export const appRouter = t.router({
hello: t.procedure
.input((value): string => {
if (typeof value === 'string') return value;
throw new Error('Input is not a string');
})
.output((value): string => {
if (typeof value === 'string') return value;
throw new Error('Output is not a string');
})
.query(({ input }) => {
return `hello ${input}`;
}),
});
Wrong:
import { z } from 'zod';
import { publicProcedure } from './trpc';
const proc = publicProcedure.input(z.string()).input(z.number());
Correct:
import { z } from 'zod';
import { publicProcedure } from './trpc';
const proc = publicProcedure
.input(z.object({ name: z.string() }))
.input(z.object({ age: z.number() }));
Multiple .input() calls merge object properties; non-object schemas (string, number, array) cannot be merged and produce type errors.
Source: www/docs/server/validators.md
Wrong:
import { z } from 'zod';
import { publicProcedure } from './trpc';
const proc = publicProcedure
.output(z.object({ id: z.string() }))
.query(() => ({ id: 123 }));
Correct:
import { z } from 'zod';
import { publicProcedure } from './trpc';
const proc = publicProcedure
.output(z.object({ id: z.string() }))
.query(() => ({ id: '123' }));
If .output() validation fails, tRPC returns INTERNAL_SERVER_ERROR (500), not BAD_REQUEST, because the server produced invalid data.
Source: www/docs/server/validators.md
Wrong:
import { z } from 'zod';
import { publicProcedure } from './trpc';
const proc = publicProcedure
.input(z.object({ cursor: z.string().optional() }))
.query(({ input }) => {
return { items: [], nextCursor: input.cursor };
});
Correct:
import { z } from 'zod';
import { publicProcedure } from './trpc';
const proc = publicProcedure
.input(z.object({ cursor: z.string().nullish() }))
.query(({ input }) => {
return { items: [], nextCursor: input.cursor };
});
React Query internally passes cursor: undefined during invalidation refetch; using .optional() without .nullable() can fail validation. Use .nullish() instead.
Source: https://github.com/trpc/trpc/issues/6862
server-setup -- initTRPC, routers, procedureserror-handling -- how validation errors surface as BAD_REQUESTerror-handling -- errorFormatter to expose Zod field errorsmiddlewares -- use input chaining with middleware base procedures