服务器端调用
你可能需要直接从托管的同一服务器调用你的过程,可以使用 createCallerFactory()
来实现此目的。这对于服务器端调用和 tRPC 过程的集成测试非常有用。
¥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.
createCaller
不应该用于从其他过程中调用过程。这会(可能)再次创建上下文、执行所有中间件并验证输入,从而产生开销 - 所有这些都已通过当前程序完成。相反,你应该将共享逻辑提取到一个单独的函数中,并从过程中调用它,如下所示:
¥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:


创建调用者
¥Create caller
使用 t.createCallerFactory
功能,你可以创建任何路由的服务器端调用者。你首先使用要调用的路由的参数调用 createCallerFactory
,然后返回一个函数,你可以在其中传入 Context
以进行以下过程调用。
¥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.
基本示例
¥Basic example
我们使用一个查询来列出帖子和一个突变来添加帖子来创建路由,然后我们调用每个方法。
¥We create the router with a query to list posts and a mutation to add posts, and then we a call each method.
ts
import {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 ();
ts
import {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 ();
集成测试中的用法示例
¥Example usage in an integration test
摘自 https://github.com/trpc/examples-next-prisma-starter/blob/main/src/server/routers/post.test.ts
¥Taken from https://github.com/trpc/examples-next-prisma-starter/blob/main/src/server/routers/post.test.ts
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);});
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()
使用 router.createCaller({})
函数(第一个参数是 Context
),我们检索 RouterCaller
的实例。
¥With the router.createCaller({})
function (first argument is Context
) we retrieve an instance of RouterCaller
.
输入查询示例
¥Input query example
我们使用输入查询创建路由,然后调用异步 greeting
过程来获取结果。
¥We create the router with an 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 ((opts ) => `Hello ${opts .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 ((opts ) => `Hello ${opts .input .name }`),});constcaller =router .createCaller ({});constresult = awaitcaller .greeting ({name : 'tRPC' });
修改例子
¥Mutation example
我们创建带有突变的路由,然后调用异步 post
过程来获取结果。
¥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 ((opts ) => {posts .push (opts .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 ((opts ) => {posts .push (opts .input );returnposts ;}),}),});constcaller =router .createCaller ({});constresult = awaitcaller .post .add ('Four');
上下文与中间件示例
¥Context with middleware example
我们创建一个中间件来在执行 secret
过程之前检查上下文。下面是两个例子:前者失败是因为上下文不适合中间件逻辑,而后者可以正常工作。
¥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.
中间件在调用任何过程之前执行。
¥Middlewares are performed before any procedure(s) are called.
ts
import {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 ();}
ts
import {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 ();}
Next.js API 端点示例
¥Example for a Next.js API endpoint
此示例演示如何在 Next.js API 端点中使用调用者。tRPC 已经为你创建了 API 端点,因此该文件仅用于展示如何从另一个自定义端点调用过程。
¥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 {appRouter } from '~/server/routers/_app';import type {NextApiRequest ,NextApiResponse } from 'next';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 {appRouter } from '~/server/routers/_app';import type {NextApiRequest ,NextApiResponse } from 'next';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 }` },});}};
错误处理
¥Error handling
createFactoryCaller
和 createCaller
函数可以通过 onError
选项获取错误处理程序。这可用于抛出未封装在 TRPCError 中的错误,或以其他方式响应错误。传递给 createCallerFactory 的任何处理程序都将在传递给 createCaller 的处理程序之前被调用。处理程序的调用参数与错误格式化程序相同,形状字段除外:
¥The createFactoryCaller
and the createCaller
function can take an error handler through the onError
option. This can be used to throw errors that are not wrapped in a TRPCError, or respond to errors in some other way. Any handler passed to createCallerFactory will be called before the handler passed to createCaller.
The handler is called with the same arguments as an error formatter would be, except for the shape field:
ts
{ctx: unknown; // The request contexterror: TRPCError; // The TRPCError that was thrownpath: string | undefined; // The path of the procedure that threw the errorinput: unknown; // The input that was passed to the proceduretype: 'query' | 'mutation' | 'subscription' | 'unknown'; // The type of the procedure that threw the error}
ts
{ctx: unknown; // The request contexterror: TRPCError; // The TRPCError that was thrownpath: string | undefined; // The path of the procedure that threw the errorinput: unknown; // The input that was passed to the proceduretype: 'query' | 'mutation' | 'subscription' | 'unknown'; // The type of the procedure that threw the error}
ts
import {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 a plain error// with the message "This is a custom error"awaitcaller .greeting ({name : 'invalid' });
ts
import {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 a plain error// with the message "This is a custom error"awaitcaller .greeting ({name : 'invalid' });