Saltar al contenido principal
Versión: 10.x

Validadores de Entrada y Salida

Traducción Beta No Oficial

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

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

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

información

Si falla la validación de salida, el servidor responderá con un 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,
// ^|
};
}),
});

El validador más básico: una función

Puedes definir un validador sin dependencias externas, usando una función.

informació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

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;

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.

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;

Con 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;

Con 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;

Con 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;

Con 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;

Con 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;

Con @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;

Con 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;

Con 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;

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í: