Languages and type safety
Zod and runtime validation
Schema-based checking of data at the edges of a system, so untrusted input is rejected before it reaches any business logic.
Zod is a small library for describing the shape of a value in code and then checking a real value against that description at runtime. The description doubles as a TypeScript type, so one schema gives you both the check and the static type, and they cannot drift apart. It sits in the same family of work as signing and verifying tokens, which is why the JWT library jose is grouped here: both are about deciding whether a piece of data that arrived from outside can be trusted.
How we use it
Jonosakti is the build where this carries the most weight. It is a public civic platform with open sign-up, dozens of user-created content types across petitions, unions, boycotts, campaigns and articles, and every one of them has a create form and an edit form posting to a REST route over D1. Each of those routes is an unauthenticated stranger's chance to send you something you did not expect. A schema at the top of the handler turns that into a 400 with a field-level message, rather than a half-written row or a stack trace.
The same code path handles sessions. Jonosakti signs and verifies its JWTs with jose, keeps session state in Workers KV, and layers CSRF tokens, a Content Security Policy, a CORS allowlist and rate limiting around the same routes. Validation is the part that decides what a request means; the token library is the part that decides whose request it is. Neither is interesting on its own, and both are the difference between a platform that can accept public writes and one that cannot.
What it costs you
Schemas are code you have to maintain, and the temptation is to write them three times: once for the database, once for the API, once for the form. That is how they get out of step. The discipline is to define the schema once, infer the TypeScript type from it, and derive the form rules from the same object, and it takes deliberate effort every time somebody adds a field in a hurry.
There is a size and speed cost. The library and its schemas ship in whatever bundle imports them, which matters on a client bundle and matters again on a Worker where the whole script has a size limit and a start-up budget. Parsing a large object on a hot path is not free, and on high-volume endpoints we validate the fields the handler actually uses rather than reconstructing the whole object.
The error output is not user copy. The default messages are precise and unfriendly, so anything user-facing needs a mapping layer from schema path to human sentence, and that layer is quietly one of the more tedious things in the codebase. Version upgrades have also been real work rather than a bump, so a project pinned to an old major stays pinned for longer than you would like.
When we would choose something else
If the data never leaves your own process, validation is theatre. Internal calls between modules in the same deployment are covered by TypeScript alone, and adding runtime checks there costs cycles to catch a class of bug the compiler already caught.
Where the shape is already defined by the database, we would rather generate from that single source than write a parallel description by hand, which is what ORMs and schema migrations exist for. Drizzle and Prisma both hand you types from the schema, and a validator derived from those beats one maintained beside them. For a form with three fields on a brochure site, the platform is enough: required, type="email" and a server-side length check, with no library at all.
Where we have used it
Every build below lists this in its stack, so the claim is checkable.