@trpc/server
Version:
485 lines (370 loc) • 13.6 kB
Markdown
---
name: validators
description: Configure input and output validation with .input() and .output() using Zod, Yup, Superstruct, ArkType, Valibot, Effect, any Standard Schema library, or custom validator functions. Chain multiple .input() calls to merge object schemas. Invalid input fails with BAD_REQUEST (the validator's error is the cause); invalid output fails with INTERNAL_SERVER_ERROR.
requires:
- server-setup
sources:
- www/v11/docs/server/validators.md
metadata:
type: core
library: trpc
library_version: 11.25.0
---
<!-- Generated from www/v11/docs/server/validators.md by www/build/skills.ts. Edit the docs instead. -->
# Input & Output Validators
From https://trpc.io/docs/server/validators
tRPC procedures may define validation logic for their input and/or output, and validators are also used to infer the types of inputs and outputs (using the [Standard Schema](https://standardschema.dev) interface if available, or custom interfaces for supported validators if not). We have first class support for many popular validators, and you can [integrate validators](https://trpc.io/docs/server/validators#contributing-your-own-validator-library) which we don't directly support.
## Input Validators
By defining an input validator, tRPC can check that a procedure call is correct and return a validation error if not.
To set up an input validator, use the `procedure.input()` method:
```ts no-check
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}`,
};
}),
});
```
If the input fails validation, the procedure doesn't run and the call fails with a `BAD_REQUEST` error whose `cause` is the error your validator threw. To send structured validation errors (e.g. Zod's field errors) to the client, use [error formatting](https://trpc.io/docs/server/error-formatting).
### Input Merging
`.input()` can be stacked to build more complex types, which is particularly useful when you want to utilise some common input to a collection of procedures in a [middleware](https://trpc.io/docs/server/middlewares).
Input merging works by spreading object properties together. This means only **object types** can be chained — non-object types (like `z.string()`) cannot be merged. If two chained `.input()` calls define the same property, the later one takes precedence. An optional input (e.g. `z.object({...}).optional()`) can only be chained after another optional input.
```ts no-check
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}`,
};
}),
});
```
## Output Validators
Validating outputs is not always as important as defining inputs, since tRPC gives you automatic type-safety by inferring the return type of your procedures. Some reasons to define an output validator include:
- Checking that data returned from untrusted sources is correct
- Ensure that you are not returning more data to the client than necessary
> [!NOTE] Info
> If output validation fails, the server will respond with an `INTERNAL_SERVER_ERROR`.
The client receives the value your output validator returns, so a schema that strips unknown keys (like `z.object()`) also strips them from the response.
```ts no-check
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 {
greeting: 'hello world',
};
}),
});
```
### Output validation of subscriptions
Since subscriptions are async iterators, you can use the same validation techniques as above.
Have a look at the [subscriptions guide](https://trpc.io/docs/server/subscriptions#output-validation) for more information.
## The most basic validator: a function
You can define a validator without any 3rd party dependencies, with a function.
> [!NOTE] Info
> We don't recommend making a custom validator unless you have a specific need, but it's important to understand that there's no magic here - it's _just typescript_!
>
> In most cases we recommend you use a [validation library](https://trpc.io/docs/server/validators#library-integrations)
```ts no-check
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;
```
## Library integrations
tRPC works out of the box with a number of popular validation and parsing libraries, including any library conforming to [Standard Schema](https://standardschema.dev). The below are some examples of usage with validators which we officially maintain support for.
### With [Zod](https://github.com/colinhacks/zod)
Zod is our default recommendation, it has a strong ecosystem which makes it a great choice to use in multiple parts of your codebase. If you have no opinion of your own and want a powerful library which won't limit future needs, Zod is a great choice.
```ts no-check
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;
```
### With [Yup](https://github.com/jquense/yup)
```ts no-check
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;
```
### With [Superstruct](https://github.com/ianstormtaylor/superstruct)
```ts no-check
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;
```
### With [scale-ts](https://github.com/paritytech/scale-ts)
```ts no-check
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;
```
### With [Typia](https://typia.io/docs/utilization/trpc/)
```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;
```
### With [ArkType](https://github.com/arktypeio/arktype#trpc)
```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;
```
### With [effect](https://effect.website/docs/schema/introduction/)
```ts no-check
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;
```
### With [Valibot](https://github.com/fabian-hiller/valibot)
```ts no-check
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 }) => {
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
```
### With [Sury](https://github.com/DZakh/sury)
```ts no-check
import { initTRPC } from '@trpc/server';
import * as S from 'sury';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(S.schema({ name: S.string }))
.output(S.schema({ greeting: S.string }))
.query(({ input }) => {
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
```
### With [@robolex/sure](https://github.com/robolex-app/public_ts)
You're able to define your own Error types and error throwing function if necessary.
As a convenience `@robolex/sure` provides [sure/src/err.ts](https://github.com/robolex-app/public_ts/blob/main/packages/sure/src/err.ts):
```ts
// sure/src/err.ts
export const err = (schema: any) => (input: any) => {
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;
```
### With [TypeBox](https://github.com/sinclairzx81/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;
```
## Contributing your own Validator Library
If you work on a validator library which supports tRPC usage, please feel free to open a PR for this page with equivalent usage to the other examples here, and a link to your docs.
Integration with tRPC in most cases is as simple as meeting one of several existing type interfaces. Conforming to [Standard Schema](https://standardschema.dev) is recommended, but in some cases we may accept a PR to add a new supported interface. Feel free to open an issue for discussion. You can check the existing supported interfaces and functions for parsing/validation [in code](https://github.com/trpc/trpc/blob/main/packages/server/src/unstable-core-do-not-import/parser.ts).
## Related skills
- `server-setup` (https://trpc.io/docs/server/overview): `npx @tanstack/intent@latest load @trpc/server#server-setup`