Having a runtime parser (which can consequently express things impossible with TypeScript types alone, like "a positive integer" rather than being satisfied with any instance of `number`) from which types are inferred, is a needed mindset shift made easy with Zod.
I use JSON Schemas for request validation and response serialisation (eg: [1]) in Fastify, derived from Zod parsers via zod-to-json-schema [2]. Some of Zod's runtime validation does not translate to JSON Schema (typically transforms), so YMMV. But this gives a good runtime glue with the static typing of request handlers.
[1] https://github.com/SocialGouv/e2esdk/blob/beta/packages/serv...
Typebox just creates JSON Schema objects at runtime and projects them into the type system with `Static<T>`. In so doing, you simultaneously create schema and types and the process of doing so is pleasant--you can just hand a TObject to Fastify as a validator object and you're done. Plus, with a Typebox type provider, it infers down to your handler.
JSON Schema is the lingua franca; to me, working in it, rather than converting to it, is a much easier proposition.
In the Zod world, if you need JSON Schema to e.g. communicate to the outside world, you can extract it from the Zod schema with https://github.com/StefanTerdell/zod-to-json-schema -- but if you don't, you don't.
JSON Schema is kind of underpowered for real validation, so if you limit yourself to it, then you'll just have a another round of validation immediately after. TypeBox' s CreateType seems to be the same idea, it cannot be expressed in just JSON Schema.
Also if you start with objects you can express runtime conditions that are not possible with types, for example maximum sizes.
Zod is quite a bit more general than JSON schema because it can express transformations not just validations.
(Or, requires an extra build step to generate that. It's beyond Typescript itself.)