Input & Output Validators | tRPC
Are there any thoughts on adding output validators for functions to complement arg validators, similar to tRPC? https://trpc.io/docs/server/validators#output-validators
I love those coz
1) they make sure the return type is not changed when you change things in functions AND
2) since Zod "parse instead of validate", it prevents leaking data from the DB that you don't want to reach the client. Which is super super valuable.
Input & Output Validators | tRPC
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. We have first class support for many popular validators, and you can integrate validators which we don't directly support.
14 Replies
The newer function declaration syntax (using
args
and handler
) was designed to support output validators, so we're definitely thinking about them!
I think if you want you could make a version of query
etc. constructors that uses Zod for output validation today. But it would mean having two different validation syntaxes, since I don't think we expose the ability to run our validators from JS outside schema/input validation.Cool, that would be great! I'll work around it now NP. I am curious, why did you decide to build your own validator over using zod? Zod is epic, parses intead of validating (huge), people know it already, and I can easily share my validators between client, server, and where ever I need them. I am sure you have your reasons, just curious.
I know Convex is very different, but I just wanted to say tRPC's API is pretty close to perfect so keep being inspired by them. It's the best API I've ever had to work with, including GraphQL.
yeah, it's a good question!
the idea is that we want the validator language to be language-agnostic. this is for a few reasons:
- we want to support great end-to-end typesafety for languages other than TS. ideally, we should be able to take the argument (and return value) validators for a function and be able to codegen high quality types for, say, Swift and Kotlin clients. this is a pretty different design constraint than zod and closer to, say, protobuf's or GraphQL's type systems.
- this then also holds for schema validators, since it's really convenient for a convex function to be able to return, e.g.,
Doc<"messages">
and have that document type autocomplete all the way to the client.
- in the future, we'd love to be able to support languages other than typescript for convex functions too! calling a function written in JS/TS and one written in, say, python should feel the same for the client, so we'd need the input/output specifications of that function to be language-agnostic.
it's definitely a tradeoff though, since there are plenty of awesome validation patterns through zod that don't fit into this cross-language approach. but, we think keeping stuff "small" at this layer will make adding, e.g. first class mobile client support easier in the near future!Gotcha! In tRPC the validator library is pluggable, so you can choose which ever you prefer. Maybe that would be a good approach for Convex too (let the user decide which validators they want to use)?
Input & Output Validators | tRPC
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. We have first class support for many popular validators, and you can integrate validators which we don't directly support.
I'm mostly talking about input and (the lack of) output validators for functions, not so much the validators in convex/schema.ts
Using Zod for schemas/input/output is really nice since you can even use regex etc. Say you have a username input or username db column and you want to make sure usernames are never empty, or only has certain chars. Then setting this in the schema is great, and you can code away in your app with confidence!
To be honest I have always thought that you should just rely on the return type of the function and shouldnt need to do any sort of output validation.
What would the purpose of output validation actually be for? You dont trust your own code? You are using third party code you dont trust?
If its the latter I would argue that you should validate locally anything that third party code (including API calls) uses and not do it at the point of "function output"..
IMO if you cant trust your own server-side code to conform to the types that you specify then you have bigger issues.
But then thats just my opinion 😛
I think the idea is more that that way you aren’t relying on inference to ensure the type of the data you’re returning is safe or correct. The inferred types could be wrong (TypeScript’s type system being unsound) and, more importantly, if you have a separate output validator you are always forced to explicitly acknowledge an output type change.
I see both sides, but it’s an interesting concept IMO
Two reasons:
1) Defining inputs and outputs first, and then write the logic, can be a nice pattern for writing any function.
2) Building for production, it is nice to know that the whole DB user object (which may hold tokens and stuff one may not want to share) isn't being sent to the client. Eg. DB types and API types are many times similar, but they are not the same - still it's easy to confuse them with each other. See point 1.
There are two layers. The DB and the API. Both have (return) types. They are often similar. But they are not the same. Specing out an API, one needs to define return (and input) types.
Oh, there are so many reasons. Imagine you modify the DB schema to add some field to a table. Now if you don't have output validation on your API this new data field may (will!) leak to the client, which may not be what you want. It's about confidence that things are not leaking, it's all about sleeping well at night.
Fair enough I see that. Thats mainly because Typescript doesnt really have an
Exact
type to ensure that that object returned exactly conforms to what you expect.
I have been following this issue for a number of years now (https://github.com/microsoft/TypeScript/issues/12936) there are a number of workarounds on there.GitHub
Exact Types · Issue #12936 · microsoft/TypeScript
This is a proposal to enable a syntax for exact types. A similar feature can be seen in Flow (https://flowtype.org/docs/objects.html#exact-object-types), but I would like to propose it as a feature...
This doesn't help you, though, if your return type were
Doc<"things">
and you add a field to that table that you don't want to expose in that public Convex function. Which relates to the idea that you might prefer to have API types that are not exactly equal to your DB types, per @Mikael Lirbank's comment aboveYe I was thinking it would look something like this:
Note that
PublicUser
contains ONLY the "name" field, and the Exact
type (if it existed) would ensure that nothing can leak out.True, that would be better!
Input and output validation via zod: https://stack.convex.dev/typescript-zod-function-validation
Using Zod with TypeScript for Server-side Validation and End-to-End...
Use Zod with TypeScript for argument validation on your server functions allows you to both protect against invalid data, and define TypeScript types ...