Zod, and for two people it is not close. Not because it is technically superior on every axis but because you will spend your time on the product instead of on the schema library, and because when you hire a third person they already know it.
The honest trade-offs:
- Zod's inferred types are the heaviest of the three at type-check time. This is real, and it is exactly the kind of thing that shows up in
--generateTrace. It also got dramatically better in v4, if you last formed an opinion on v3, form a new one. - Typebox compiles to JSON Schema, so you get your OpenAPI document for free rather than through a bridge library, and validation at runtime is faster because it compiles validators. The developer experience is worse and the error messages are worse.
- Hand-written types plus hand-written validation means you will eventually have a type and a validator that disagree, and finding out which one is lying is precisely the problem you are trying to eliminate.
Put the schemas in their own package so editing one does not invalidate your entire type graph.