如何优雅地使用 zod - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
FlashEcho
V2EX    程序员

如何优雅地使用 zod

  •  
  •   FlashEcho 1 天前 1511 次点击

    使用 zod 的时候,下面几个很难受的地方:

    • 数据库的 orm 框架用的是 drizzle ,这个时候得先用 drizzle 定义数据库 schema ,没法优先使用 zod 定义 schema ,导出成 drizzle 数据库表

    • 由于在数据库端 drizzle 优先了,在后端服务端有两种选择:

      1. 直接用 drizzle-zod 导出的 insert, select 等 schema ,后端服务直接用这个导出的 schema ,这样最方便,但是这个 schema 无形中变宽松了,有大量 optional ,而且如果有多种 insert 情况,就失去了严格的约束
      2. 再在后端手写一套 zod schema ,但是这样就会有大量重复和冗余,而且程序员需要自己手动维护 zod schema 和数据库 schema 的同步。这实际上是一个逻辑或者说业务上的实体,但是没法从一个唯一来源只写一次导出到处用,在服务和数据库上重复写了两次,也很难受

    请问各位大佬是怎么处理这种问题的

    4 条回复    2026-01-07 15:58:53 +08:00
    huijiewei
        1
    huijiewei  
       1 天前   1
    没什么冗余的说法

    持久层的 model 和服务层的 model 还是建议区分开.哪怕重复也要分开
    Ketteiron
        2
    Ketteiron  
       1 天前   3
    以 drizzle 的数据库 schema 为第一优先级,在此之上派生下一级的用于接口校验、表单验证的 schema 。
    现代化的 typescript 工程,要严格遵守 DRY ,不允许在任何地方出现多次重复定义,分离数据库层与业务层实际上与复用逻辑并不冲突,没有写两遍的必要。
    无论何时,应该只有一个唯一来源,应用于整个项目,包括前端以及所有中间件服务,并强制保证运行时类型与 ts 类型完全等同。

    基于这个理论,不应该直接使用 createInsertSchema 和 createUpdateSchema ,因为它们也是在重复定义并不明确的类型,应该封装一个方法统一给所有下一级 schema 使用。

    它可能长这样 const createSchema = (table,labels)=> createInsertSchema(table).pick(labels)
    具体代码不贴了,有一些类型体操,需要保证输入限制以及 pick 生效

    举个例子,我定义好一张表
    export const userTable = pgTable('user', {
    ...baseTable,
    username: varchar({ length: 32 }).notNull().unique(),
    password: varchar({ length: 64 }).notNull(),
    })
    然后这么使用
    export const userLoginSchema = createSchema(userTable, {
    username: (s) => s.min(4),
    password: (s) => s.min(6),
    })
    因为返回的是一个 zod 对象,可以按照业务逻辑进行 extend 不在数据库中的其他参数,或者根据业务进行覆盖

    第一参数接收一个数据库表,以此限制第二参数输入的键名,value 是一个函数,参数是经过 drizzle-zod 生成后的 schema
    drizzle-zod 会为 varchar({ length: 32 }) 这样的定义自动生成 z.string().max(32),但是它不会限制最小长度,因此派生的 schema 需要在原来基础上进行补充,或者也可以直接传入一个 zod 对象覆盖。

    不应该对业务层暴露一大堆的 optional ,而是严格限定你只能传递什么东西过来,它要么必须有值要么禁止传递,从数据库层一层一层往下传递到所有涉及到的地方,一直到达前端表单,这才是 schema("契约")的正确用法之一。

    还有个地方需要注意,这个 schema 不能直接在 monorepo 中被前端/中间件引入,因为包含完整的数据库模式以及用不到的大量运行时函数,直接 import 大概会打包出两百多 k ,需要使用 json-to-zod + zod-to-json(zod4 最新版已内置) 等变通方法,监听 schema 文件自动生成一个新文件并 export 给其他项目。当然因为无法序列化函数,所以 refine 等功能都用不了。

    此外我对 createSchema 进行了大量改造,包括强制传递元数据,并在路由/表单通过包装 safeParseAsync 函数解析出适合人类阅读的提示信息:'用户名长度最低为 4 位'、'密码长度最低 6 位'、'缺少 xxx 字段'、'xxx 格式错误',其中'用户名'和'密码'就是我要求传递的提示文本,它同样只能定义一次,还可以进行 i18n 改造,所有其它信息可以通过遍历 code 、origin 、input 等进行填充,社区里大量使用的 zod i18n 库是一个不理想的变通方法,但图省事也可以去用。
    FlashEcho
        3
    FlashEcho  
    OP
       1 天前
    @Ketteiron #2 谢谢大佬,看来还是我对于 drizzle-zod 的使用不够深入,不然应该可以少在在 api 处定义很多重复的 schema

    希望什么时候 drizzle-zod 支持先用 zod 定义 schema 再转成数据库 schema 就好了,zod 的表现能力强,可以削弱一层给 drizzle 用,也能原封不动给 api 处用,天然好做成单一事实来源 。drizzle 表现能力弱,先定义好数据库 schema ,再加一层约束给 api 处用就很难让普通用户一下自然领会最佳实践
    wings110
        4
    wings110  
       12 小时 26 分钟前
    @Ketteiron 谢谢佬!解决了我一直以来的困惑!!
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     940 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 19ms UTC 20:25 PVG 04:25 LAX 12:25 JFK 15:25
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86