Server Side Calls
You may need to call your procedure(s) directly from the server, createCaller()
function returns you an instance of RouterCaller
able to execute queries and mutations.
Create caller
With the router.createCaller({})
function (first argument is Context
) we retrieve an instance of RouterCaller
.
Input query example
We create the router with a input query and then we call the asynchronous greeting
procedure to get the result.
ts
import {initTRPC } from '@trpc/server';import {z } from 'zod';constt =initTRPC .create ();constrouter =t .router ({// Create procedure at path 'greeting'greeting :t .procedure .input (z .object ({name :z .string () })).query (({input }) => `Hello ${input .name }`),});constcaller =router .createCaller ({});constresult = awaitcaller .greeting ({name : 'tRPC' });
ts
import {initTRPC } from '@trpc/server';import {z } from 'zod';constt =initTRPC .create ();constrouter =t .router ({// Create procedure at path 'greeting'greeting :t .procedure .input (z .object ({name :z .string () })).query (({input }) => `Hello ${input .name }`),});constcaller =router .createCaller ({});constresult = awaitcaller .greeting ({name : 'tRPC' });
Mutation example
We create the router with a mutation and then we call the asynchronous post
procedure to get the result.
ts
import {initTRPC } from '@trpc/server';import {z } from 'zod';constposts = ['One', 'Two', 'Three'];constt =initTRPC .create ();constrouter =t .router ({post :t .router ({add :t .procedure .input (z .string ()).mutation (({input }) => {posts .push (input );returnposts ;}),}),});constcaller =router .createCaller ({});constresult = awaitcaller .post .add ('Four');
ts
import {initTRPC } from '@trpc/server';import {z } from 'zod';constposts = ['One', 'Two', 'Three'];constt =initTRPC .create ();constrouter =t .router ({post :t .router ({add :t .procedure .input (z .string ()).mutation (({input }) => {posts .push (input );returnposts ;}),}),});constcaller =router .createCaller ({});constresult = awaitcaller .post .add ('Four');
Context with middleware example
We create a middleware to check the context before execute secret
procedure.
Below two examples, the former fails because the context doesn't fit the middleware logic the latter works correctly.
Middlewares are performed before any procedure(s) are called.
ts
import {TRPCError ,initTRPC } from '@trpc/server';typeContext = {user ?: {id : string;};};constt =initTRPC .context <Context >().create ();constisAuthed =t .middleware (({next ,ctx }) => {if (!ctx .user ) {throw newTRPCError ({code : 'UNAUTHORIZED',message : 'You are not authorized',});}returnnext ({ctx : {// Infers that the `user` is non-nullableuser :ctx .user ,},});});constprotectedProcedure =t .procedure .use (isAuthed );constrouter =t .router ({secret :protectedProcedure .query (({ctx }) =>ctx .user ),});{// ❌ this will return an error because there isn't the right context paramconstcaller =router .createCaller ({});constresult = awaitcaller .secret ();}{// ✅ this will work because user property is present inside context paramconstauthorizedCaller =router .createCaller ({user : {id : 'KATT',},});constresult = awaitauthorizedCaller .secret ();}
ts
import {TRPCError ,initTRPC } from '@trpc/server';typeContext = {user ?: {id : string;};};constt =initTRPC .context <Context >().create ();constisAuthed =t .middleware (({next ,ctx }) => {if (!ctx .user ) {throw newTRPCError ({code : 'UNAUTHORIZED',message : 'You are not authorized',});}returnnext ({ctx : {// Infers that the `user` is non-nullableuser :ctx .user ,},});});constprotectedProcedure =t .procedure .use (isAuthed );constrouter =t .router ({secret :protectedProcedure .query (({ctx }) =>ctx .user ),});{// ❌ this will return an error because there isn't the right context paramconstcaller =router .createCaller ({});constresult = awaitcaller .secret ();}{// ✅ this will work because user property is present inside context paramconstauthorizedCaller =router .createCaller ({user : {id : 'KATT',},});constresult = awaitauthorizedCaller .secret ();}
Example for a Next.js API endpoint
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.
ts
import {TRPCError } from '@trpc/server';import {getHTTPStatusCodeFromError } from '@trpc/server/http';import type {NextApiRequest ,NextApiResponse } from 'next';import {appRouter } from '~/server/routers/_app';typeResponseData = {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. */constpostId = `this-id-does-not-exist-${Math .random ()}`;constcaller =appRouter .createCaller ({});try {// the server-side callconstpostResult = awaitcaller .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 instanceofTRPCError ) {// We can get the specific HTTP status code coming from tRPC (e.g. 404 for `NOT_FOUND`).consthttpStatusCode =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 }` },});}};
ts
import {TRPCError } from '@trpc/server';import {getHTTPStatusCodeFromError } from '@trpc/server/http';import type {NextApiRequest ,NextApiResponse } from 'next';import {appRouter } from '~/server/routers/_app';typeResponseData = {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. */constpostId = `this-id-does-not-exist-${Math .random ()}`;constcaller =appRouter .createCaller ({});try {// the server-side callconstpostResult = awaitcaller .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 instanceofTRPCError ) {// We can get the specific HTTP status code coming from tRPC (e.g. 404 for `NOT_FOUND`).consthttpStatusCode =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 }` },});}};