www/versioned_docs/version-10.x/server/server-side-calls.md
You may need to call your procedure(s) directly from the same server they're hosted in, createCallerFactory() can be used to achieve this. This is useful for server-side calls and for integration testing of your tRPC procedures.
:::info
createCaller should not be used to call procedures from within other procedures. This creates overhead by (potentially) creating context again, executing all middlewares, and validating the input - all of which were already done by the current procedure. Instead, you should extract the shared logic into a separate function and call that from within the procedures, like so:
:::
With the t.createCallerFactory-function you can create a server-side caller of any router. You first call createCallerFactory with an argument of the router you want to call, then this returns a function where you can pass in a Context for the following procedure calls.
We create the router with a query to list posts and a mutation to add posts, and then we a call each method.
// @target: esnext
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
type Context = {
foo: string;
};
const t = initTRPC.context<Context>().create();
const publicProcedure = t.procedure;
const { createCallerFactory, router } = t;
interface Post {
id: string;
title: string;
}
const posts: Post[] = [
{
id: '1',
title: 'Hello world',
},
];
const appRouter = router({
post: router({
add: publicProcedure
.input(
z.object({
title: z.string().min(2),
}),
)
.mutation((opts) => {
const post: Post = {
...opts.input,
id: `${Math.random()}`,
};
posts.push(post);
return post;
}),
list: publicProcedure.query(() => posts),
}),
});
// 1. create a caller-function for your router
const createCaller = createCallerFactory(appRouter);
// 2. create a caller using your `Context`
const caller = createCaller({
foo: 'bar',
});
// 3. use the caller to add and list posts
const addedPost = await caller.post.add({
title: 'How to make server-side call in tRPC',
});
const postList = await caller.post.list();
// ^?
Taken from https://github.com/trpc/examples-next-prisma-starter/blob/main/src/server/routers/post.test.ts
import { inferProcedureInput } from '@trpc/server';
import { createContextInner } from '../context';
import { AppRouter, createCaller } from './_app';
test('add and get post', async () => {
const ctx = await createContextInner({});
const caller = createCaller(ctx);
const input: inferProcedureInput<AppRouter['post']['add']> = {
text: 'hello test',
title: 'hello test',
};
const post = await caller.post.add(input);
const byId = await caller.post.byId({ id: post.id });
expect(byId).toMatchObject(input);
});
router.createCaller():::caution
router.createCaller() has been deprecated and will be removed in v11 or v12 of tRPC.
:::
With the router.createCaller({}) function (first argument is Context) we retrieve an instance of RouterCaller.
We create the router with an input query, and then we call the asynchronous greeting procedure to get the result.
// @target: esnext
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const router = t.router({
// Create procedure at path 'greeting'
greeting: t.procedure
.input(z.object({ name: z.string() }))
.query((opts) => `Hello ${opts.input.name}`),
});
const caller = router.createCaller({});
const result = await caller.greeting({ name: 'tRPC' });
// ^?
We create the router with a mutation, and then we call the asynchronous post procedure to get the result.
// @target: esnext
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const posts = ['One', 'Two', 'Three'];
const t = initTRPC.create();
const router = t.router({
post: t.router({
add: t.procedure.input(z.string()).mutation((opts) => {
posts.push(opts.input);
return posts;
}),
}),
});
const caller = router.createCaller({});
const result = await caller.post.add('Four');
// ^?
We create a middleware to check the context before executing the secret procedure. Below are two examples: the former fails because the context doesn't fit the middleware logic, and the latter works correctly.
:::info
Middlewares are performed before any procedure(s) are called.
:::
// @target: esnext
import { initTRPC, TRPCError } from '@trpc/server';
type Context = {
user?: {
id: string;
};
};
const t = initTRPC.context<Context>().create();
const protectedProcedure = t.procedure.use((opts) => {
const { ctx } = opts;
if (!ctx.user) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'You are not authorized',
});
}
return opts.next({
ctx: {
// Infers that the `user` is non-nullable
user: ctx.user,
},
});
});
const router = t.router({
secret: protectedProcedure.query((opts) => opts.ctx.user),
});
{
// ❌ this will return an error because there isn't the right context param
const caller = router.createCaller({});
const result = await caller.secret();
}
{
// ✅ this will work because user property is present inside context param
const authorizedCaller = router.createCaller({
user: {
id: 'KATT',
},
});
const result = await authorizedCaller.secret();
// ^?
}
:::tip
This example shows how to use the caller in a Next.js API endpoint. tRPC creates API endpoints for you already, so this file is only meant to show how to call a procedure from another, custom endpoint.
:::
// @noErrors
// ---cut---
import { TRPCError } from '@trpc/server';
import { getHTTPStatusCodeFromError } from '@trpc/server/http';
import { appRouter } from '~/server/routers/_app';
import type { NextApiRequest, NextApiResponse } from 'next';
type ResponseData = {
data?: {
postTitle: string;
};
error?: {
message: string;
};
};
export default async (
req: NextApiRequest,
res: NextApiResponse<ResponseData>,
) => {
/** We want to simulate an error, so we pick a post ID that does not exist in the database. */
const postId = `this-id-does-not-exist-${Math.random()}`;
const caller = appRouter.createCaller({});
try {
// the server-side call
const postResult = await caller.post.byId({ id: postId });
res.status(200).json({ data: { postTitle: postResult.title } });
} catch (cause) {
// If this a tRPC error, we can extract additional information.
if (cause instanceof TRPCError) {
// We can get the specific HTTP status code coming from tRPC (e.g. 404 for `NOT_FOUND`).
const httpStatusCode = getHTTPStatusCodeFromError(cause);
res.status(httpStatusCode).json({ error: { message: cause.message } });
return;
}
// This is not a tRPC error, so we don't have specific information.
res.status(500).json({
error: { message: `Error while accessing post with ID ${postId}` },
});
}
};