Saltar al contenido principal
Versión: 11.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. Los validadores también se utilizan para inferir los tipos de entradas y salidas (usando la interfaz Standard Schema cuando está disponible, o interfaces personalizadas para validadores admitidos). Ofrecemos soporte de primera clase para muchos validadores populares, y puedes integrar validadores que no admitamos 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
// 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;
const name: string
return {
greeting: `Hello ${opts.input.name}`,
};
}),
});
ts
// 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;
const name: string
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
const baseProcedure = t.procedure
.input(z.object({ townName: z.string() }))
.use((opts) => {
const input = opts.input;
const input: { townName: string; }
 
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;
const input: { townName: string; name: string; }
return {
greeting: `Hello ${input.name}, my friend from ${input.townName}`,
};
}),
});
ts
const baseProcedure = t.procedure
.input(z.object({ townName: z.string() }))
.use((opts) => {
const input = opts.input;
const input: { townName: string; }
 
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;
const input: { townName: string; name: string; }
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
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
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,
           
};
}),
});

Validación de salida en suscripciones

Dado que las suscripciones son iteradores asíncronos, puedes usar las mismas técnicas de validación mencionadas anteriormente.

Consulta la guía de suscripciones para más información.

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;
const input: string
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;
const input: string
return `hello ${input}`;
}),
});
 
export type AppRouter = typeof appRouter;

Integraciones de bibliotecas

tRPC funciona de inmediato con varias bibliotecas populares de validación y análisis, incluyendo cualquier biblioteca que cumpla con Standard Schema. A continuación se muestran 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 }) => {
(parameter) input: { name: string; }
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 }) => {
(parameter) input: { name: string; }
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 }) => {
(parameter) input: { name: string; }
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 }) => {
(parameter) input: { name: string; }
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 }) => {
(parameter) input: { name: string; }
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 }) => {
(parameter) input: { name: string; }
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 }) => {
(parameter) input: { readonly name: string; }
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 }) => {
(parameter) input: { readonly name: string; }
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' })).query((opts) => {
return {
greeting: `hello ${opts.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' })).query((opts) => {
return {
greeting: `hello ${opts.input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

Con effect

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

Con Valibot

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

Con @robolex/sure

Si es necesario, puedes definir tus propios tipos de error y funciones para lanzar errores. Como conveniencia, @robolex/sure proporciona sure/src/err.ts:

ts
// sure/src/err.ts
export const err = (schema) => (input) => {
const [good, result] = schema(input);
if (good) return result;
throw result;
};
ts
// sure/src/err.ts
export const err = (schema) => (input) => {
const [good, result] = schema(input);
if (good) return result;
throw result;
};
ts
import { err, object, string } from '@robolex/sure';
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(
err(
object({
name: string,
}),
),
)
.output(
err(
object({
greeting: string,
}),
),
)
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { err, object, string } from '@robolex/sure';
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(
err(
object({
name: string,
}),
),
)
.output(
err(
object({
greeting: string,
}),
),
)
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

Con TypeBox

ts
import { Type } from '@sinclair/typebox';
import { initTRPC } from '@trpc/server';
import { wrap } from '@typeschema/typebox';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(wrap(Type.Object({ name: Type.String() })))
.output(wrap(Type.Object({ greeting: Type.String() })))
.query(({ input }) => {
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { Type } from '@sinclair/typebox';
import { initTRPC } from '@trpc/server';
import { wrap } from '@typeschema/typebox';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(wrap(Type.Object({ name: Type.String() })))
.output(wrap(Type.Object({ greeting: Type.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 generalmente es tan sencilla como cumplir con una de varias interfaces de tipos existentes. Se recomienda ajustarse a Standard Schema, aunque en algunos casos podemos aceptar un PR para agregar una nueva interfaz compatible. Siéntete libre de abrir un issue para discutirlo. Puedes consultar las interfaces y funciones de análisis/validación compatibles actualmente en el código.