Clean definition of models using Zod schemas
My Current Situation
My Zod schemas are my single source of truth regarding the structure of models used in my app because they are definable in a very detailed way and can even be used for runtime validation.
Since there are usually multiple versions of each model (with id, when I fetch entities from my api; without id, when i create a new one; ...) I came up with a structure like this:
Although this already seemed a bit more complicated than I think it might be possible, it worked for the time being.
Since my models are of course a bit more complicated than this example, I want to add refines to them.
But when adding one to my base schema, I can't omit or pick them anymore since the whole schema is no longer a
ZodObject
but a ZodEffect
which doesn't support those methods.
I mean I could keep my old, unrefined version of that model as the base, and add refines later on on top of each single version of my model (create, update, ...), but that seems way too complicated and brings a bad DX since I should only need to define those things once.
The Question
Now I want to know how do you guys define your models? Do you also use Zod schemas, and if so, how do you structure them to solve issues like mine? Or do you have a completely different approach?3 Replies
The way that I handled ids was to make this:
And then attach it to anything that needs an id
I did something similar with updatedAt / createdAt timestamps, which ended up being very helpful when I changed them from
z.date()
to z.coerce.date()
once that feature came out, didn't have to change it in a dozen places.
As for your problem with refines, it is possible to add a ZodEffect
as a property in a ZodObject
, if that helps you solve some of the issue.
Regarding the refinement:
I need a refinement on the whole object since I want to add a validation regarding multiple (nested) props
My case is something like this
And I want to add a refinement that checks that
amount
is >= the sum all of childrens' amounts
In that case I have to refine my base schema, but can't extend / omit / pick on it later on since refine
changes its type to ZodEffect
Alright, I searched a bit in the Zod discussions on GH and found this thread which 100% nailed my problem with refined schemas:
https://github.com/colinhacks/zod/discussions/694
The contributors know it's now optimal, but explained very well there why they chose the way they did, so I actually go this way now:
1. Define base schema
2. Extend / omit the base schema wherever I need it
3. Create a central function to add my
.refine
to a minimally abstracted version of the schema I need for it, and add it on top of the transformed base schema
It's not perfect in regards of DX, but it does what it's supposed to do.
Hopefully my comment will gather some feedback though and maybe trigger an update in some future:
https://github.com/colinhacks/zod/discussions/694#discussioncomment-4926037GitHub
Using
.extend
after doing .superRefine
. · Discussion #694 · col...You can .extend() from a z.object(...), but not if you've added a refinement. Example (playground link): import * as z from 'zod'; const userSchema = z.object({ name: z.stri...