Aller au contenu principal
Version : 10.x

Appels côté serveur

Traduction Bêta Non Officielle

Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →

Vous pourriez avoir besoin d'appeler vos procédures directement depuis le serveur où elles sont hébergées. La fonction createCallerFactory() permet d'y parvenir. Cela est utile pour les appels côté serveur et pour les tests d'intégration de vos procédures tRPC.

info

createCaller ne doit pas être utilisé pour appeler des procédures depuis d'autres procédures. Cela génère une surcharge en recréant (potentiellement) le contexte, exécutant tous les middlewares et validant les entrées - opérations déjà effectuées par la procédure courante. Extrayez plutôt la logique partagée dans une fonction distincte que vous appellerez depuis les procédures, comme suit :

Créer un appelant

La fonction t.createCallerFactory permet de créer un appelant côté serveur pour n'importe quel routeur. Vous appelez d'abord createCallerFactory avec comme argument le routeur ciblé, ce qui retourne une fonction à laquelle vous pouvez passer un Context pour les appels de procédure ultérieurs.

Exemple basique

Nous créons un routeur avec une requête pour lister les articles et une mutation pour en ajouter, puis nous appelons chaque méthode.

ts
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();
const postList: Post[]
ts
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();
const postList: Post[]

Exemple d'utilisation dans un test d'intégration

Tiré de 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()

attention

router.createCaller() est dépréciée et sera supprimée dans la version v11 ou v12 de tRPC.

La fonction router.createCaller({}) (premier argument = Context) retourne une instance de RouterCaller.

Exemple de requête avec entrée

Nous créons un routeur avec une requête d'entrée, puis appelons la procédure asynchrone greeting pour obtenir le résultat.

ts
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' });
const result: string
ts
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' });
const result: string

Exemple de mutation

Nous créons un routeur avec une mutation, puis appelons la procédure asynchrone post pour obtenir le résultat.

ts
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');
const result: string[]
ts
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');
const result: string[]

Exemple de contexte avec middleware

Nous créons un middleware pour vérifier le contexte avant d'exécuter la procédure secret. Deux exemples suivent : le premier échoue car le contexte ne correspond pas à la logique du middleware, le second fonctionne correctement.


info

Les middlewares s'exécutent avant toute procédure appelée.


ts
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();
const result: { id: string; }
}
ts
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();
const result: { id: string; }
}

Exemple pour un point de terminaison API Next.js

astuce

Cet exemple montre comment utiliser l'appelant dans un point de terminaison API Next.js. tRPC crée déjà des points de terminaison API pour vous, donc ce fichier sert uniquement à illustrer comment appeler une procédure depuis un autre point de terminaison personnalisé.

ts
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}` },
});
}
};
ts
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}` },
});
}
};