Validadores de Entrada y Salida
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Los procedimientos de tRPC pueden definir lógica de validación para sus entradas y/o salidas, y los validadores también se utilizan para inferir los tipos de estas entradas y salidas. Ofrecemos soporte de primera clase para muchos validadores populares, y puedes integrar validadores que no soportamos directamente.
Validadores de Entrada
Al definir un validador de entrada, tRPC puede verificar que una llamada a procedimiento sea correcta y devolver un error de validación si no lo es.
Para configurar un validador de entrada, usa el método procedure.input():
ts// @target: esnextimport { initTRPC } from '@trpc/server';// ---cut---// Our examples use Zod by default, but usage with other libraries is identicalimport { 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: esnextimport { initTRPC } from '@trpc/server';// ---cut---// Our examples use Zod by default, but usage with other libraries is identicalimport { 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}`,};}),});
Fusión de Entradas
Se pueden encadenar múltiples .input() para construir tipos más complejos, lo cual es especialmente útil cuando deseas utilizar entradas comunes para un conjunto de procedimientos en un middleware.
ts// @target: esnextimport { 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: esnextimport { 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}`,};}),});
Validadores de Salida
Validar las salidas no siempre es tan importante como definir las entradas, ya que tRPC te ofrece seguridad de tipos automática al inferir el tipo de retorno de tus procedimientos. Algunas razones para definir un validador de salida incluyen:
-
Verificar que los datos devueltos de fuentes no confiables sean correctos
-
Asegurarse de no devolver más datos al cliente de los necesarios
Si falla la validación de salida, el servidor responderá con un INTERNAL_SERVER_ERROR.
ts// @target: esnextimport { 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: esnextimport { 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,// ^|};}),});
El validador más básico: una función
Puedes definir un validador sin dependencias externas, usando una función.
No recomendamos crear un validador personalizado a menos que tengas una necesidad específica, pero es importante entender que aquí no hay magia: ¡es solo TypeScript!
En la mayoría de los casos recomendamos usar una biblioteca de validación
tsimport { 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;
tsimport { 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;
Integraciones de bibliotecas
tRPC funciona sin configuración adicional con varias bibliotecas populares de validación y análisis. A continuación se muestran algunos ejemplos de uso con validadores para los que mantenemos soporte oficial.
Con Zod
Zod es nuestra recomendación predeterminada. Tiene un ecosistema sólido que lo convierte en una excelente opción para usar en múltiples partes de tu base de código. Si no tienes una preferencia propia y quieres una biblioteca potente que no limite necesidades futuras, Zod es una gran elección.
tsimport { 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;
tsimport { 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;
Con Yup
tsimport { 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;
tsimport { 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;
Con Superstruct
tsimport { 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;
tsimport { 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;
Con scale-ts
tsimport { 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;
tsimport { 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;
Con Typia
tsimport { 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;
tsimport { 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;
Con ArkType
tsimport { 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;
tsimport { 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;
Con @effect/schema
tsimport * 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;
tsimport * 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;
Con runtypes
tsimport { 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;
tsimport { 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;
Con Valibot
tsimport { 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;
tsimport { 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;
Contribuir con tu propia biblioteca de validación
Si trabajas en una biblioteca de validación que admite tRPC, no dudes en abrir un PR para esta página con un ejemplo de uso equivalente a los demás aquí mostrados, junto con un enlace a tu documentación.
La integración con tRPC en la mayoría de los casos es tan simple como implementar una de las interfaces de tipo existentes, aunque en algunos casos podemos aceptar un PR para agregar una nueva interfaz admitida. No dudes en abrir un issue para discutirlo. Puedes consultar las interfaces admitidas actualmente aquí: