ProvenPin Install

tRPC 11 + Zod router with server-side caller: working versions (tested 2026-10-15)

A typed tRPC router with Zod inputs, an auth middleware, and tests through createCaller.

passed3 passed · 0 failed2026-10-15 13:18 UTC
Status
passed
Last passed
2026-10-15 13:18 UTC
Tests
3 / 3
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
@trpc/server@11.19.0zod@4.6.7tsx@4.23.15@types/node@24.19.2
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38740169402

The code

Exactly the files that passed. Your agent can pull them with get_recipe("trpc-zod-router") (install), or see them on GitHub.

package.json
{
  "name": "trpc-zod-router",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "@trpc/server": "11.19.0",
    "zod": "4.6.7"
  },
  "devDependencies": {
    "tsx": "4.23.15",
    "@types/node": "24.19.2"
  }
}
src/router.ts
import { initTRPC, TRPCError } from "@trpc/server";
import { z } from "zod";

type Context = { userId: string | null };
const t = initTRPC.context<Context>().create();

const authed = t.procedure.use(({ ctx, next }) => {
  if (!ctx.userId) throw new TRPCError({ code: "UNAUTHORIZED" });
  return next({ ctx: { userId: ctx.userId } });
});

const notes: { id: number; owner: string; text: string }[] = [];

export const appRouter = t.router({
  hello: t.procedure.input(z.object({ name: z.string() })).query(({ input }) => `hello ${input.name}`),
  addNote: authed.input(z.object({ text: z.string().min(1) })).mutation(({ ctx, input }) => {
    const note = { id: notes.length + 1, owner: ctx.userId, text: input.text };
    notes.push(note);
    return note;
  }),
});
export type AppRouter = typeof appRouter;
export const createCaller = t.createCallerFactory(appRouter);
test/router.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { createCaller } from "../src/router.ts";

test("public query", async () => {
  assert.equal(await createCaller({ userId: null }).hello({ name: "dev" }), "hello dev");
});
test("authed mutation", async () => {
  const note = await createCaller({ userId: "u1" }).addNote({ text: "hi" });
  assert.equal(note.owner, "u1");
});
test("unauthorized and invalid input", async () => {
  await assert.rejects(createCaller({ userId: null }).addNote({ text: "x" }), /UNAUTHORIZED/);
  await assert.rejects(createCaller({ userId: "u1" }).addNote({ text: "" }));
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.