[{"data":1,"prerenderedAt":298},["ShallowReactive",2],{"content:\u002Fposts\u002F2025\u002Fdrizzle-in-monorepo":3,"surround:\u002Fposts\u002F2025\u002Fdrizzle-in-monorepo":286},{"id":4,"title":5,"body":6,"canonical":264,"categories":265,"date":267,"description":268,"draft":269,"extension":270,"image":264,"meta":271,"navigation":272,"path":273,"permalink":264,"readingTime":274,"recommend":264,"references":264,"seo":279,"sitemap":280,"stem":281,"tags":282,"type":264,"updated":267,"__hash__":285},"content\u002Fposts\u002F2025\u002Fdrizzle-in-monorepo.md","利用 monorepo 在项目中共享 drizzle schema 定义和类型",{"type":7,"value":8,"toc":250},"minimark",[9,13,16,21,29,39,43,54,63,72,76,87,94,101,104,110,117,120,127,134,145,152,155,162,165,170,177,180,187,201,206,210,216,222,225,231,238,241,244,247],[10,11,12],"p",{},"我想要前后端可以共享数据库 schema 的类型，这样的话，在更改数据库 schema 后。我们不需要修改类型文件，直接使用 drizzle 生成的类型即可。本文我们就一起看看如何实现这个功能吧！",[10,14,15],{},"首先我们要准备一个 momorepo 的环境，目前使用 pnpm 可以很方便的实现。",[17,18,20],"h2",{"id":19},"初始化-monorepo-环境","初始化 monorepo 环境",[10,22,23,24,28],{},"创建一个 ",[25,26,27],"code",{"code":27},"pnpm-workspace.yaml","文件，配置如下：",[30,31,37],"pre",{"className":32,"code":34,"language":35,"meta":36},[33],"language-yaml","packages:\n  - 'packages\u002F*'\n  - 'apps\u002F*'\n","yaml","",[25,38,34],{"__ignoreMap":36},[17,40,42],{"id":41},"创建-shared-包用来放置前后端公用的代码","创建 shared 包用来放置前后端公用的代码",[10,44,45,46,49,50,53],{},"我在 packages 中放置可以复用的包。我的数据库schema文件放在 ",[25,47,48],{"code":48},"packages\u002Fshared"," 中。并使用 ",[25,51,52],{"code":52},"tsdown"," 来作为打包工具使用。",[30,55,61],{"className":56,"code":58,"filename":59,"language":60,"meta":36},[57],"language-json","{\n  \"name\": \"@labs\u002Fshared\",\n  \"type\": \"module\",\n  \"version\": \"1.0.0\",\n  \"description\": \"前后端共享的包\",\n  \"exports\": {\n    \".\": {\n      \"types\": \".\u002Fdist\u002Findex.d.ts\",\n      \"import\": \".\u002Fdist\u002Findex.js\",\n      \"require\": \".\u002Fdist\u002Findex.js\",\n    },\n  },\n  \"main\": \".\u002Fdist\u002Findex.js\",\n  \"module\": \".\u002Fdist\u002Findex.js\",\n  \"types\": \".\u002Fdist\u002Findex.d.ts\",\n  \"files\": [\n    \"dist\",\n  ],\n  \"scripts\": {\n    \"build\": \"tsdown\",\n    \"dev\": \"tsdown --watch\",\n    \"lint\": \"eslint . --fix\",\n  },\n  \"dependencies\": {\n    \"drizzle-orm\": \"^0.44.4\",\n    \"drizzle-zod\": \"^0.8.3\",\n    \"zod\": \"^4.0.17\",\n  },\n}\n","package.json","json",[25,62,58],{"__ignoreMap":36},[30,64,70],{"className":65,"code":67,"filename":68,"language":69,"meta":36},[66],"language-ts","import { defineConfig } from 'tsdown'\n\nexport default defineConfig({\n    entry: ['.\u002Fsrc\u002Findex.ts'],\n    dts: true,\n    format: ['esm'],\n    outDir: 'dist',\n    shims: true,\n    target: 'es2020',\n    platform: 'browser',\n    external: [\n        'node:*',\n    ],\n})\n","tsdown.config.ts","ts",[25,71,67],{"__ignoreMap":36},[17,73,75],{"id":74},"创建数据库-schema-文件并导出","创建数据库 schema 文件并导出",[10,77,78,79,82,83,86],{},"接下来我们就可以在 ",[25,80,81],{"code":81},"src\u002Fschame"," 中写我们的 ",[25,84,85],{"code":85},"drizzle"," 了，我这里就用一个user的schema来做示例",[10,88,89,90,93],{},"我们这里规定所有schema文件都要以 ",[25,91,92],{"code":92},"entity.ts"," 结尾，方面后面使用",[30,95,99],{"className":96,"code":97,"filename":98,"language":69,"meta":36},[66],"import { relations } from 'drizzle-orm';\nimport { pgTable, serial, varchar } from 'drizzle-orm\u002Fpg-core';\nimport { createInsertSchema, createSelectSchema } from 'drizzle-zod';\nimport { z } from 'zod';\n\nimport { timestamps } from '.\u002Fglobal';\n\nexport const user = pgTable('user', {\n    id: serial().primaryKey(),\n    username: varchar().notNull().unique(),\n    password: varchar().notNull(),\n    ...timestamps,\n});\n\nexport const selectUserSchema = createSelectSchema(user);\nexport type SelectUser = z.infer\u003Ctypeof selectUserSchema>;\nexport const insertUserSchema = createInsertSchema(user)\nexport type InsertUser = z.infer\u003Ctypeof insertUserSchema>\n","schema\u002Fuser.entity.ts",[25,100,97],{"__ignoreMap":36},[10,102,103],{},"timestamps内容如下",[105,106,107],"blockquote",{},[10,108,109],{},"我们几乎每个表都会有创建时间和修改时间，抽离出来方便复用",[30,111,115],{"className":112,"code":113,"filename":114,"language":69,"meta":36},[66],"import { timestamp } from 'drizzle-orm\u002Fpg-core';\n\nexport const timestamps = {\n    createdAt: timestamp('created_at').notNull().defaultNow(),\n    updatedAt: timestamp('updated_at').notNull().defaultNow(),\n};\n","schema\u002Fglobal.ts",[25,116,113],{"__ignoreMap":36},[10,118,119],{},"我们把它们都导出",[30,121,125],{"className":122,"code":123,"filename":124,"language":69,"meta":36},[66],"import { user } from '.\u002Fuser.entity'\n\nexport * from '.\u002Fuser.entity'\n\n\u002F\u002F 这里组装好并导出方便在API端直接使用\nexport const schema = {\n    user\n}\n","schema\u002Findex.ts",[25,126,123],{"__ignoreMap":36},[30,128,132],{"className":129,"code":130,"filename":131,"language":69,"meta":36},[66],"export * from '.\u002Fschema' \u002F\u002F 导出schema的配置和类型，供其他包使用\n","src\u002Findex.ts",[25,133,130],{"__ignoreMap":36},[10,135,136,137,140,141,144],{},"然后就可以构建使用了，在开发环境我们可以使用刚才编写好的 ",[25,138,139],{"code":139},"dev"," 脚本（它会使用 tsdown --watch 监听文件的变化实时编译），打包时使用 ",[25,142,143],{"code":143},"build","脚本。",[10,146,147,148,151],{},"在这个monorepo仓库中的其他包如果要使用，就使用 ",[25,149,150],{"code":150},"pnpm add --workspace @labs\u002Fshared"," 安装，安装后就可以导入使用啦。",[17,153,154],{"id":154},"在其他环境中使用",[10,156,157,158,161],{},"我这里有一个fetch的包用来放置所有的API请求文件，使用tanstack-query作为请求库。一个API包用来创建所有接口。我们分别在两个包中安装刚才创建好的 ",[25,159,160],{"code":160},"@labs\u002Fshared"," 包。",[10,163,164],{},"我这里就展示一些重要的代码，具体代码就不展示了。在API端，我们需要根据导出的schema来创建连接。在fetch包中，我们需要导出的类型来进行请求或者响应的类型约束，方便前端对接时使用。",[166,167,169],"h3",{"id":168},"在-api-端使用","在 API 端使用",[30,171,175],{"className":172,"code":173,"filename":174,"language":69,"meta":36},[66],"import 'dotenv\u002Fconfig';\nimport { defineConfig } from 'drizzle-kit';\n\nexport default defineConfig({\n    schema: '..\u002F..\u002Fpackages\u002Fshared\u002Fsrc\u002Fschema\u002F*.entity.ts',\n    out: '.\u002Fdrizzle\u002Fmigrations',\n    dialect: 'postgresql',\n    dbCredentials: {\n        url: process.env.DATABASE_URL!,\n    },\n});\n","apps\u002Fapi\u002Fdrizzle.config.ts",[25,176,173],{"__ignoreMap":36},[10,178,179],{},"创建drizzle migrate需要的文件",[30,181,185],{"className":182,"code":183,"filename":184,"language":69,"meta":36},[66],"import { schema } from '@labs\u002Fshared'\nimport { drizzle, NodePgDatabase } from 'drizzle-orm\u002Fnode-postgres';\nimport { Pool } from 'pg';\n\nimport env from '@\u002Futils\u002Fenv'\n\nconst connectionString = env.database.DATABASE_URL;\nconst pool = new Pool({ connectionString });\n\nconst db = drizzle(pool, { schema })\n\nexport default db\n","apps\u002Fapi\u002Fdrizzle\u002Findex.ts",[25,186,183],{"__ignoreMap":36},[10,188,189,190,193,194,197,198,200],{},"这样就可以使用了，我们可以使用 ",[25,191,192],{"code":192},"npx drizzle-kit generate","生成数据库迁移文件，并使用 ",[25,195,196],{"code":196},"npx drizzle-kit migrate"," 来进行数据库迁移。其余增删改查的操作就和以前一样，只不过schema相关的文件记得要在 ",[25,199,160],{"code":160}," 中导入。",[105,202,203],{},[10,204,205],{},"pnpm monorepo 和 ts 结合起来的话编辑器会自动提示，这个还是很方便的。",[166,207,209],{"id":208},"在-fetch-包中使用","在 fetch 包中使用",[30,211,214],{"className":212,"code":213,"language":69,"meta":36},[66],"import type { InsertUser, SelectUser } from '@labs\u002Fshared'\nimport type { AxiosError } from 'axios'\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack\u002Fvue-query'\n\nimport type { IResponse } from '.\u002Fuse-axios' \u002F\u002F 这里构建响应都有的类型，使用泛型可以方便的将不一致的地方进行类型填充\n\n\u002F\u002F 查询用户列表\nexport function useGetUserListQuery() {\n    const { axiosInstance } = useAxios()\n    return useQuery\u003CIResponse\u003CSelectUser[]>, AxiosError>({\n        queryKey: ['userList'],\n        queryFn: async () => {\n            const response = await axiosInstance.get('\u002Fv1\u002Fuser')\n            return response.data\n        },\n    })\n}\n\n\u002F\u002F 注册\nexport function useRegisterMutation() {\n    const { axiosInstance } = useAxios()\n\n    return useMutation\u003CIResponse\u003C{ accessToken: string, refreshToken: string }>, AxiosError, InsertUser>({\n        mutationFn: async (data: InsertUser) => {\n            const response = await axiosInstance.post('\u002Fv1\u002Fauth\u002Fregister', { data })\n            return response.data\n        },\n    })\n}\n",[25,215,213],{"__ignoreMap":36},[217,218,219],"alert",{},[10,220,221],{},"这里抽离fetch包的作用是如果你有两个或者几个不同的端，可以同时使用这个包进行对API的请求。同事们或者你自己使用的时候可以只关注请求那个接口，请求的过程和处理的过程会在这个包中完成。",[17,223,224],{"id":224},"这样做的好处",[10,226,227,228,230],{},"我们把数据库schema和它对应的生成的类型都抽离到 ",[25,229,48],{"code":48}," 中，如果要用到相关的类型就直接用它生成的就好了，没必要再重新生成一份。到时候其他的类型相关的包也可以放到这里，在这里类型修改后，前后端使用到的地方都会进行类型校验并进行错误提示。",[10,232,233,234,237],{},"请求相关的包都要抽离到 ",[25,235,236],{"code":236},"packages\u002Ffetch"," 包中，如果哪个地方要请求API，你就可以使用这个包了，所有API端暴露出来的接口都会在这里使用tanstack query操作一次返回，你可以不用管请求的细节，只关注传参和结果即可。而且使用tanstack query，他会自动生成响应式数据，有请求状态的变化和接口缓存，之前手动封装的许多包就可以废弃掉了。",[10,239,240],{},"因为这些包都有ts的类型限制，所以在API响应改变或者请求改变后，这里改了后用的地方都会有类型不一致的提示，配合构建前进行类型校验，我们可以方便的找出错误，避免在API改变后忘记更改前端请求参数或者响应造成的错误。",[17,242,243],{"id":243},"总结",[10,245,246],{},"有了这套东西后，如果你的公司使用node作为BFF层或者你开一个新的node项目，可以推广这一种方法。我们可以把几种东西都分开，可以践行关注点分离的思想。而且有完备的类型提示和校验。",[10,248,249],{},"或许你有什么别的想法？欢迎在下方留言一起讨论！",{"title":36,"searchDepth":251,"depth":251,"links":252},4,[253,255,256,257,262,263],{"id":19,"depth":254,"text":20},2,{"id":41,"depth":254,"text":42},{"id":74,"depth":254,"text":75},{"id":154,"depth":254,"text":154,"children":258},[259,261],{"id":168,"depth":260,"text":169},3,{"id":208,"depth":260,"text":209},{"id":224,"depth":254,"text":224},{"id":243,"depth":254,"text":243},null,[266],"代码","2025-09-11 12:31:08","还蛮好用的！",false,"md",{},true,"\u002Fposts\u002F2025\u002Fdrizzle-in-monorepo",{"text":275,"minutes":276,"time":277,"words":278},"8 min read",7.785,467100,1557,{"title":5,"description":268},{"loc":273},"posts\u002F2025\u002Fdrizzle-in-monorepo",[283,85,284],"monorepo","tanstack","6jLI1xVIZvnfTrVWl6aKGESdV425B8Hs8oVVcHH8III",[287,293],{"title":288,"path":289,"stem":290,"date":291,"type":292,"children":-1},"为什么我在 Nodejs 中选择 drizzle","\u002Fposts\u002F2025\u002Fwhy-di-i-choose-drizzle-orm-in-nodejs","posts\u002F2025\u002Fwhy-di-i-choose-drizzle-orm-in-nodejs","2025-09-02 11:09:24","story",{"title":294,"path":295,"stem":296,"date":297,"type":264,"children":-1},"五分钟内免费集成 CodeRabbit 到你的项目中","\u002Fposts\u002F2025\u002Fcoderabbitai-use","posts\u002F2025\u002Fcoderabbitai-use","2025-09-13 16:43:24",1789642588659]