packages/server/skills/server-side-calls/SKILL.md
// server/trpc.ts
import { initTRPC } from '@trpc/server';
type Context = { user?: { id: string } };
const t = initTRPC.context<Context>().create();
export const router = t.router;
export const publicProcedure = t.procedure;
export const createCallerFactory = t.createCallerFactory;
// server/appRouter.ts
import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { createCallerFactory, publicProcedure, router } from './trpc';
interface Post {
id: string;
title: string;
}
const posts: Post[] = [{ id: '1', title: 'Hello world' }];
export const appRouter = router({
post: router({
add: publicProcedure
.input(z.object({ title: z.string().min(2) }))
.mutation(({ input }) => {
const post: Post = { ...input, id: `${Math.random()}` };
posts.push(post);
return post;
}),
byId: publicProcedure
.input(z.object({ id: z.string() }))
.query(({ input }) => {
const post = posts.find((p) => p.id === input.id);
if (!post) throw new TRPCError({ code: 'NOT_FOUND' });
return post;
}),
list: publicProcedure.query(() => posts),
}),
});
export type AppRouter = typeof appRouter;
export const createCaller = createCallerFactory(appRouter);
// usage
import { createCaller } from './appRouter';
const caller = createCaller({ user: { id: '1' } });
const postList = await caller.post.list();
const newPost = await caller.post.add({ title: 'New post' });
// server/appRouter.test.ts
import type { inferProcedureInput } from '@trpc/server';
import { createCaller } from './appRouter';
import type { AppRouter } from './appRouter';
import { createContextInner } from './context';
async function testAddAndGetPost() {
const ctx = await createContextInner({ user: undefined });
const caller = createCaller(ctx);
const input: inferProcedureInput<AppRouter['post']['add']> = {
title: 'Test post',
};
const post = await caller.post.add(input);
const allPosts = await caller.post.list();
console.assert(allPosts.some((p) => p.id === post.id));
}
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
greeting: t.procedure
.input(z.object({ name: z.string() }))
.query(({ input }) => `Hello ${input.name}`),
});
const caller = appRouter.createCaller({});
const result = await caller.greeting({ name: 'tRPC' });
import { TRPCError } from '@trpc/server';
import { getHTTPStatusCodeFromError } from '@trpc/server/http';
import type { NextApiRequest, NextApiResponse } from 'next';
import { appRouter } from './appRouter';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const caller = appRouter.createCaller({});
try {
const post = await caller.post.byId({ id: req.query.id as string });
res.status(200).json({ data: { postTitle: post.title } });
} catch (cause) {
if (cause instanceof TRPCError) {
const httpStatusCode = getHTTPStatusCodeFromError(cause);
res.status(httpStatusCode).json({ error: { message: cause.message } });
return;
}
res.status(500).json({ error: { message: 'Internal server error' } });
}
}
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
greeting: t.procedure
.input(z.object({ name: z.string() }))
.query(({ input }) => {
if (input.name === 'invalid') {
throw new Error('Invalid name');
}
return `Hello ${input.name}`;
}),
});
const caller = appRouter.createCaller(
{},
{
onError: (opts) => {
console.error('An error occurred:', opts.error);
},
},
);
Wrong:
import { appRouter } from './appRouter';
import { createCallerFactory, publicProcedure } from './trpc';
const createCaller = createCallerFactory(appRouter);
const proc = publicProcedure.query(async () => {
const caller = createCaller({});
return caller.post.list();
});
Correct:
import type { Context } from './context';
import { publicProcedure } from './trpc';
async function listPosts(ctx: Context) {
return ctx.db.post.findMany();
}
const proc = publicProcedure.query(async ({ ctx }) => {
return listPosts(ctx);
});
Calling createCaller from within a procedure re-creates context, re-runs all middleware, and re-validates input; extract shared logic into a plain function instead.
Source: www/docs/server/server-side-calls.md
Wrong:
import { appRouter } from './appRouter';
const caller = appRouter.createCaller({});
await caller.protectedRoute();
// middleware throws UNAUTHORIZED because ctx.user is undefined
Correct:
import { appRouter } from './appRouter';
import { createContextInner } from './context';
const ctx = await createContextInner({ user: { id: '1' } });
const caller = appRouter.createCaller(ctx);
await caller.protectedRoute();
createCaller requires a context object matching what procedures and middleware expect; passing an empty object when procedures require auth context causes runtime errors.
Source: www/docs/server/server-side-calls.md
server-setup -- initTRPC, routers, context configurationmiddlewares -- auth middleware that callers must satisfyerror-handling -- TRPCError and getHTTPStatusCodeFromError