Aller au contenu principal
Version : 10.x

Validateurs d'entrée et de sortie

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 →

Les procédures tRPC peuvent définir une logique de validation pour leurs entrées et/ou sorties. Ces validateurs servent également à inférer les types des données traitées. Nous offrons un support natif pour de nombreuses bibliothèques de validation populaires, et vous pouvez intégrer d'autres validateurs non directement supportés.

Validateurs d'entrée

En définissant un validateur d'entrée, tRPC peut vérifier qu'un appel de procédure est correct et renvoyer une erreur de validation le cas échéant.

Pour configurer un validateur d'entrée, utilisez la méthode procedure.input() :

ts
// @target: esnext
import { initTRPC } from '@trpc/server';
// ---cut---
// Our examples use Zod by default, but usage with other libraries is identical
import { z } from 'zod';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(
z.object({
name: z.string(),
}),
)
.query((opts) => {
const name = opts.input.name;
// ^?
return {
greeting: `Hello ${opts.input.name}`,
};
}),
});
ts
// @target: esnext
import { initTRPC } from '@trpc/server';
// ---cut---
// Our examples use Zod by default, but usage with other libraries is identical
import { z } from 'zod';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(
z.object({
name: z.string(),
}),
)
.query((opts) => {
const name = opts.input.name;
// ^?
return {
greeting: `Hello ${opts.input.name}`,
};
}),
});

Fusion des entrées

.input() peut être chaîné pour construire des types plus complexes, ce qui est particulièrement utile lorsque vous souhaitez réutiliser une entrée commune pour plusieurs procédures via un middleware.

ts
// @target: esnext
import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
// ---cut---
const baseProcedure = t.procedure
.input(z.object({ townName: z.string() }))
.use((opts) => {
const input = opts.input;
// ^?
console.log(`Handling request with user from: ${input.townName}`);
return opts.next();
});
export const appRouter = t.router({
hello: baseProcedure
.input(
z.object({
name: z.string(),
}),
)
.query((opts) => {
const input = opts.input;
// ^?
return {
greeting: `Hello ${input.name}, my friend from ${input.townName}`,
};
}),
});
ts
// @target: esnext
import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
// ---cut---
const baseProcedure = t.procedure
.input(z.object({ townName: z.string() }))
.use((opts) => {
const input = opts.input;
// ^?
console.log(`Handling request with user from: ${input.townName}`);
return opts.next();
});
export const appRouter = t.router({
hello: baseProcedure
.input(
z.object({
name: z.string(),
}),
)
.query((opts) => {
const input = opts.input;
// ^?
return {
greeting: `Hello ${input.name}, my friend from ${input.townName}`,
};
}),
});

Validateurs de sortie

La validation des sorties n'est pas toujours aussi cruciale que celle des entrées, car tRPC offre une sécurité de type automatique en inférant le type de retour de vos procédures. Voici quelques raisons de définir un validateur de sortie :

  • Vérifier que les données renvoyées par des sources non fiables sont correctes

  • Garantir que vous ne renvoyez pas plus de données que nécessaire au client

info

Si la validation de sortie échoue, le serveur répondra avec une erreur INTERNAL_SERVER_ERROR.

ts
// @target: esnext
import { initTRPC } from '@trpc/server';
// @noErrors
// ---cut---
import { z } from 'zod';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.output(
z.object({
greeting: z.string(),
}),
)
.query((opts) => {
return {
gre,
// ^|
};
}),
});
ts
// @target: esnext
import { initTRPC } from '@trpc/server';
// @noErrors
// ---cut---
import { z } from 'zod';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.output(
z.object({
greeting: z.string(),
}),
)
.query((opts) => {
return {
gre,
// ^|
};
}),
});

Le validateur le plus basique : une fonction

Vous pouvez définir un validateur sans dépendances tierces, à l'aide d'une simple fonction.

info

Nous ne recommandons pas de créer un validateur personnalisé sans besoin spécifique, mais il est important de comprendre qu'il n'y a aucune magie ici - c'est simplement du TypeScript !

Dans la plupart des cas, nous vous conseillons d'utiliser une bibliothèque de validation.

ts
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Input is not a string');
})
.output((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Output is not a string');
})
.query((opts) => {
const { input } = opts;
// ^?
return `hello ${input}`;
}),
});
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Input is not a string');
})
.output((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Output is not a string');
})
.query((opts) => {
const { input } = opts;
// ^?
return `hello ${input}`;
}),
});
export type AppRouter = typeof appRouter;

Intégrations de bibliothèques

tRPC fonctionne immédiatement avec plusieurs bibliothèques de validation et d'analyse populaires. Voici quelques exemples d'utilisation avec des validateurs officiellement supportés.

Avec Zod

Zod est notre recommandation par défaut. Son écosystème robuste en fait un excellent choix pour une utilisation dans différentes parties de votre codebase. Si vous n'avez pas de préférence et cherchez une bibliothèque puissante sans limitations futures, Zod est idéal.

ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(
z.object({
name: z.string(),
}),
)
.output(
z.object({
greeting: z.string(),
}),
)
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(
z.object({
name: z.string(),
}),
)
.output(
z.object({
greeting: z.string(),
}),
)
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

Avec Yup

ts
import { initTRPC } from '@trpc/server';
import * as yup from 'yup';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(
yup.object({
name: yup.string().required(),
}),
)
.output(
yup.object({
greeting: yup.string().required(),
}),
)
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import * as yup from 'yup';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(
yup.object({
name: yup.string().required(),
}),
)
.output(
yup.object({
greeting: yup.string().required(),
}),
)
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

Avec Superstruct

ts
import { initTRPC } from '@trpc/server';
import { object, string } from 'superstruct';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(object({ name: string() }))
.output(object({ greeting: string() }))
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import { object, string } from 'superstruct';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(object({ name: string() }))
.output(object({ greeting: string() }))
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

Avec scale-ts

ts
import { initTRPC } from '@trpc/server';
import * as $ from 'scale-codec';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input($.object($.field('name', $.str)))
.output($.object($.field('greeting', $.str)))
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import * as $ from 'scale-codec';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input($.object($.field('name', $.str)))
.output($.object($.field('greeting', $.str)))
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

Avec Typia

ts
import { initTRPC } from '@trpc/server';
import typia from 'typia';
import { v4 } from 'uuid';
import { IBbsArticle } from '../structures/IBbsArticle';
const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
store: publicProcedure
.input(typia.createAssert<IBbsArticle.IStore>())
.output(typia.createAssert<IBbsArticle>())
.query(({ input }) => {
return {
id: v4(),
writer: input.writer,
title: input.title,
body: input.body,
created_at: new Date().toString(),
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import typia from 'typia';
import { v4 } from 'uuid';
import { IBbsArticle } from '../structures/IBbsArticle';
const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
store: publicProcedure
.input(typia.createAssert<IBbsArticle.IStore>())
.output(typia.createAssert<IBbsArticle>())
.query(({ input }) => {
return {
id: v4(),
writer: input.writer,
title: input.title,
body: input.body,
created_at: new Date().toString(),
};
}),
});
export type AppRouter = typeof appRouter;

Avec ArkType

ts
import { initTRPC } from '@trpc/server';
import { type } from 'arktype';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(type({ name: 'string' }).assert)
.output(type({ greeting: 'string' }).assert)
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import { type } from 'arktype';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(type({ name: 'string' }).assert)
.output(type({ greeting: 'string' }).assert)
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

Avec @effect/schema

ts
import * as Schema from '@effect/schema/Schema';
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(Schema.decodeUnknownSync(Schema.Struct({ name: Schema.String })))
.output(
Schema.decodeUnknownSync(Schema.Struct({ greeting: Schema.String })),
)
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import * as Schema from '@effect/schema/Schema';
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(Schema.decodeUnknownSync(Schema.Struct({ name: Schema.String })))
.output(
Schema.decodeUnknownSync(Schema.Struct({ greeting: Schema.String })),
)
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

Avec runtypes

ts
import { initTRPC } from '@trpc/server';
import * as T from 'runtypes';
const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(T.Record({ name: T.String }))
.output(T.Record({ greeting: T.String }))
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import * as T from 'runtypes';
const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(T.Record({ name: T.String }))
.output(T.Record({ greeting: T.String }))
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

Avec Valibot

ts
import { wrap } from '@decs/typeschema';
import { initTRPC } from '@trpc/server';
import { object, string } from 'valibot';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(wrap(object({ name: string() })))
.output(wrap(object({ greeting: string() })))
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { wrap } from '@decs/typeschema';
import { initTRPC } from '@trpc/server';
import { object, string } from 'valibot';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(wrap(object({ name: string() })))
.output(wrap(object({ greeting: string() })))
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

Contribuer avec votre propre bibliothèque de validation

Si vous développez une bibliothèque de validation prenant en charge tRPC, n'hésitez pas à ouvrir une PR pour cette page avec un exemple d'utilisation équivalent aux autres exemples présentés, ainsi qu'un lien vers votre documentation.

L'intégration avec tRPC se résume généralement à implémenter l'une des interfaces existantes. Dans certains cas, nous pouvons accepter une PR pour ajouter une nouvelle interface supportée. N'hésitez pas à ouvrir une issue pour discussion. Consultez les interfaces actuellement supportées :