Llamadas desde el servidor
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Puede que necesites llamar a tus procedimientos directamente desde el mismo servidor donde están alojados. Para esto puedes usar createCallerFactory(). Esto es útil para llamadas desde el servidor y para pruebas de integración de tus procedimientos tRPC.
No debes usar createCaller para llamar procedimientos desde dentro de otros procedimientos. Esto genera sobrecarga al (potencialmente) crear contexto nuevamente, ejecutar todos los middlewares y validar el input, acciones que ya realizó el procedimiento actual. En su lugar, debes extraer la lógica compartida en una función separada y llamarla desde los procedimientos, así:


Crear un llamador
Con la función t.createCallerFactory puedes crear un llamador del lado del servidor para cualquier router. Primero llamas a createCallerFactory con el router que quieres llamar como argumento, luego esto devuelve una función donde puedes pasar un Context para las llamadas posteriores a procedimientos.
Ejemplo básico
Creamos el router con una query para listar posts y una mutación para agregar posts, y luego llamamos a cada método.
tsimport {initTRPC } from '@trpc/server';import {z } from 'zod';typeContext = {foo : string;};constt =initTRPC .context <Context >().create ();constpublicProcedure =t .procedure ;const {createCallerFactory ,router } =t ;interfacePost {id : string;title : string;}constposts :Post [] = [{id : '1',title : 'Hello world',},];constappRouter =router ({post :router ({add :publicProcedure .input (z .object ({title :z .string ().min (2),}),).mutation ((opts ) => {constpost :Post = {...opts .input ,id : `${Math .random ()}`,};posts .push (post );returnpost ;}),list :publicProcedure .query (() =>posts ),}),});// 1. create a caller-function for your routerconstcreateCaller =createCallerFactory (appRouter );// 2. create a caller using your `Context`constcaller =createCaller ({foo : 'bar',});// 3. use the caller to add and list postsconstaddedPost = awaitcaller .post .add ({title : 'How to make server-side call in tRPC',});constpostList = awaitcaller .post .list ();
tsimport {initTRPC } from '@trpc/server';import {z } from 'zod';typeContext = {foo : string;};constt =initTRPC .context <Context >().create ();constpublicProcedure =t .procedure ;const {createCallerFactory ,router } =t ;interfacePost {id : string;title : string;}constposts :Post [] = [{id : '1',title : 'Hello world',},];constappRouter =router ({post :router ({add :publicProcedure .input (z .object ({title :z .string ().min (2),}),).mutation ((opts ) => {constpost :Post = {...opts .input ,id : `${Math .random ()}`,};posts .push (post );returnpost ;}),list :publicProcedure .query (() =>posts ),}),});// 1. create a caller-function for your routerconstcreateCaller =createCallerFactory (appRouter );// 2. create a caller using your `Context`constcaller =createCaller ({foo : 'bar',});// 3. use the caller to add and list postsconstaddedPost = awaitcaller .post .add ({title : 'How to make server-side call in tRPC',});constpostList = awaitcaller .post .list ();
Ejemplo de uso en una prueba de integración
Tomado de https://github.com/trpc/examples-next-prisma-starter/blob/main/src/server/routers/post.test.ts
tsasync functiontestAddAndGetPost () {constctx = awaitcreateContextInner ({});constcaller =createCaller (ctx );constinput :inferProcedureInput <AppRouter ['post']['add']> = {text : 'hello test',title : 'hello test',};constpost = awaitcaller .post .add (input );constbyId = awaitcaller .post .byId ({id :post .id });}
tsasync functiontestAddAndGetPost () {constctx = awaitcreateContextInner ({});constcaller =createCaller (ctx );constinput :inferProcedureInput <AppRouter ['post']['add']> = {text : 'hello test',title : 'hello test',};constpost = awaitcaller .post .add (input );constbyId = awaitcaller .post .byId ({id :post .id });}
router.createCaller()
Con la función router.createCaller({}) (donde el primer argumento es Context) obtenemos una instancia de RouterCaller.
Ejemplo de query con input
Creamos el router con una query que recibe input, y luego llamamos al procedimiento asíncrono greeting para obtener el resultado.
tsimport {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 ((opts ) => `Hello ${opts .input .name }`),});constcaller =router .createCaller ({});constresult = awaitcaller .greeting ({name : 'tRPC' });
tsimport {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 ((opts ) => `Hello ${opts .input .name }`),});constcaller =router .createCaller ({});constresult = awaitcaller .greeting ({name : 'tRPC' });
Ejemplo de mutación
Creamos el router con una mutación, y luego llamamos al procedimiento asíncrono post para obtener el resultado.
tsimport {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 ((opts ) => {posts .push (opts .input );returnposts ;}),}),});constcaller =router .createCaller ({});constresult = awaitcaller .post .add ('Four');
tsimport {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 ((opts ) => {posts .push (opts .input );returnposts ;}),}),});constcaller =router .createCaller ({});constresult = awaitcaller .post .add ('Four');
Ejemplo de contexto con middleware
Creamos un middleware para verificar el contexto antes de ejecutar el procedimiento secret. A continuación se muestran dos ejemplos: el primero falla porque el contexto no cumple con la lógica del middleware, y el segundo funciona correctamente.
Los middlewares se ejecutan antes de que se llame a cualquier procedimiento.
tsimport {initTRPC ,TRPCError } from '@trpc/server';typeContext = {user ?: {id : string;};};constt =initTRPC .context <Context >().create ();constprotectedProcedure =t .procedure .use ((opts ) => {const {ctx } =opts ;if (!ctx .user ) {throw newTRPCError ({code : 'UNAUTHORIZED',message : 'You are not authorized',});}returnopts .next ({ctx : {// Infers that the `user` is non-nullableuser :ctx .user ,},});});constrouter =t .router ({secret :protectedProcedure .query ((opts ) =>opts .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 ();}
tsimport {initTRPC ,TRPCError } from '@trpc/server';typeContext = {user ?: {id : string;};};constt =initTRPC .context <Context >().create ();constprotectedProcedure =t .procedure .use ((opts ) => {const {ctx } =opts ;if (!ctx .user ) {throw newTRPCError ({code : 'UNAUTHORIZED',message : 'You are not authorized',});}returnopts .next ({ctx : {// Infers that the `user` is non-nullableuser :ctx .user ,},});});constrouter =t .router ({secret :protectedProcedure .query ((opts ) =>opts .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 ();}
Ejemplo para un endpoint API de Next.js
Este ejemplo muestra cómo usar el llamador en un endpoint API de Next.js. tRPC ya crea endpoints API por ti, así que este archivo solo pretende mostrar cómo llamar un procedimiento desde otro endpoint personalizado.
tstypeResponseData = {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 }` },});}};
tstypeResponseData = {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 }` },});}};
Manejo de errores
Las funciones createCallerFactory y createCaller pueden recibir un manejador de errores mediante la opción onError. Esto permite lanzar errores que no están envueltos en un TRPCError o responder a errores de otras maneras. Cualquier manejador pasado a createCallerFactory se ejecutará antes que el manejador pasado a createCaller.
El manejador recibe los mismos argumentos que un formateador de errores, excepto por el campo shape:
tsinterfaceOnErrorShape {ctx : unknown;error :TRPCError ;path : string | undefined;input : unknown;type : 'query' | 'mutation' | 'subscription' | 'unknown';}
tsinterfaceOnErrorShape {ctx : unknown;error :TRPCError ;path : string | undefined;input : unknown;type : 'query' | 'mutation' | 'subscription' | 'unknown';}
tsimport {initTRPC } from '@trpc/server';import {z } from 'zod';constt =initTRPC .context <{foo ?: 'bar';}>().create ();constrouter =t .router ({greeting :t .procedure .input (z .object ({name :z .string () })).query ((opts ) => {if (opts .input .name === 'invalid') {throw newError ('Invalid name');}return `Hello ${opts .input .name }`;}),});constcaller =router .createCaller ({/* context */},{onError : (opts ) => {console .error ('An error occurred:',opts .error );},},);// The following will log "An error occurred: Error: Invalid name", and then throw the errorawaitcaller .greeting ({name : 'invalid' });
tsimport {initTRPC } from '@trpc/server';import {z } from 'zod';constt =initTRPC .context <{foo ?: 'bar';}>().create ();constrouter =t .router ({greeting :t .procedure .input (z .object ({name :z .string () })).query ((opts ) => {if (opts .input .name === 'invalid') {throw newError ('Invalid name');}return `Hello ${opts .input .name }`;}),});constcaller =router .createCaller ({/* context */},{onError : (opts ) => {console .error ('An error occurred:',opts .error );},},);// The following will log "An error occurred: Error: Invalid name", and then throw the errorawaitcaller .greeting ({name : 'invalid' });