Nuxt 4 集成 Drizzle ORM (PostgreSQL) 完整教程

h1🚀 Nuxt 4 集成 Drizzle ORM (PostgreSQL) 完整教程blockquotep适用版本tabletheadtrth依赖th版本th备注tbodytrtdDrizzle ORMtdstrongv1.0.0-alpha.xtd本文基于 alpha.10，后续版本 API 可能微调trtdpgtdstrongv8tdPostgreSQL 驱动blockquotep⚠️ strong注意：Drizzle ORM 目前仍处于 alpha 阶段，如果你使用更新版本，建议参考a官方文档。detailssummary📋教程范围与前置要求p本教程专注 strongPostgreSQL + Nuxt 4 的 Drizzle ORM 集成。如果您使用 MySQL/SQLite，驱动和类型会有差异，请参考官方文档相应部分。pstrong前置要求：ulli已有一个 Nuxt 4 项目（codenpm create nuxt@latest ）li本地已安装 PostgreSQL（或使用云数据库）li了解 TypeScript 基础hrh2⚠️ 核心区别：Drizzle 官方文档 vs Nuxt 集成tabletheadtrth对比维度thDrizzle 官方文档th本教程（Nuxt 4）tbodytrtdstrong项目类型td普通 Node.js 项目tdNuxt 4（基于 Nitro 服务器）trtdstrong目录结构td自由定义td严格遵循 codeserver/ 目录规范trtdstrong数据库连接td直接导出 codedb 实例td通过 codeserver/db/index 导出 codeuseDB()trtdstrongSchema 组织td通常单个文件td建议拆分多文件，并strong必须包含关系定义trtdstrong运行环境td手动执行脚本td通过 API 路由触发，由 Nitro 管理pstrong关键点：strong完全照搬官方文档会在 Nuxt 中失败，因为 Nuxt 的服务端目录结构和自动导入机制与普通 Node 项目不同。hrh2📦 第一步：安装依赖precodespanspan# 生产依赖 spanspanpnpmspan addspan drizzle-ormspan pg spanspan# 开发依赖 spanspanpnpmspan addspan -Dspan drizzle-kitspan @types/pgspan dotenv tabletheadtrth包名th作用tbodytrtdcodedrizzle-ormtdORM 核心，提供类型安全的查询trtdcodepgtdPostgreSQL 驱动trtdcodedrizzle-kittd迁移工具，自动生成 SQLtrtdcode@types/pgtdTypeScript 类型（用于 pg）trtdcodedotenvtd开发时从 code.env 加载环境变量hrh2🔐 第二步：环境变量与配置h31. 创建 code.env 文件（strong必须加入 code.gitignore）precodespanspan# .env spanspanNUXT_DATABASE_URL=postgresql://postgres:yourpassword@localhost:5432/yourdb blockquotep确保 URL 格式正确：codepostgresql://用户名:密码@主机:端口/数据库名h32. 配置 codenuxt.config.tsprecodespanspanexportspan defaultspan defineNuxtConfigspan(span{ spanspan runtimeConfigspan:span { spanspan databaseUrlspan:span processspan.spanenvspan.spanNUXT_DATABASE_URLspan,span // 无默认值，强制从环境变量读取 spanspan }, spanspan // ... 其他配置 spanspan}span)span; blockquotepstrong重要：codedatabaseUrl 必须从环境变量读取，不留默认值，避免生产环境误连本地数据库。hrh2📁 第三步：组织 Schema 文件（核心）pNuxt 项目中，建议将所有数据库相关文件放在 codeserver/db/ 下。strong必须同时包含表定义和关系定义。h3目录结构precodeserver/ ├── db/ │ ├── schema/ │ │ ├── users.ts │ │ ├── comments.ts │ │ ├── relations.ts │ │ └── index.ts # 统一导出 │ └── index.ts # 数据库连接 h33.1 定义表（以 users 和 comments 为例）pstrongcodeserver/db/schema/users.tsprecodespanspanimportspan { spanspan pgTablespan, spanspan serialspan, spanspan varcharspan, spanspan booleanspan, spanspan timestampspan, spanspan}span fromspan "spandrizzle-orm/pg-corespan"span; spanspan spanspanexportspan constspan users span=span pgTablespan(span"spanusersspan"span,span { spanspan idspan:span serialspan(span"spanidspan"span)span.spanprimaryKeyspan()span, spanspan githubIdspan:span varcharspan(span"spangithub_idspan"span,span {span lengthspan:span 39span }span)span.spannotNullspan()span.spanuniquespan()span, spanspan usernamespan:span varcharspan(span"spanusernamespan"span,span {span lengthspan:span 100span }span)span.spannotNullspan()span, spanspan isAdminspan:span booleanspan(span"spanis_adminspan"span)span.spandefaultspan(spanfalsespan)span, spanspan createdAtspan:span timestampspan(span"spancreated_atspan"span,span {span withTimezonespan:span truespan }span)span.spandefaultNowspan()span, spanspan}span)span; spanspan spanspanexportspan typespan Userspan =span typeofspan usersspan.span$inferSelectspan; spanspanexportspan typespan NewUserspan =span typeofspan usersspan.span$inferInsertspan; pstrongcodeserver/db/schema/comments.tsprecodespanspanimportspan { spanspan pgTablespan, spanspan serialspan, spanspan integerspan, spanspan textspan, spanspan varcharspan, spanspan timestampspan, spanspan}span fromspan "spandrizzle-orm/pg-corespan"span; spanspanimportspan {span usersspan }span fromspan "span./usersspan"span; spanspan spanspanexportspan constspan comments span=span pgTablespan(span"spancommentsspan"span,span { spanspan idspan:span serialspan(span"spanidspan"span)span.spanprimaryKeyspan()span, spanspan userIdspan:span integerspan(span"spanuser_idspan"span)span.spanreferencesspan(span()span =>span usersspan.spanidspan,span { spanspan onDeletespan:span "spanset nullspan"span, spanspan }span)span, spanspan contentspan:span textspan(span"spancontentspan"span)span.spannotNullspan()span, spanspan permalinkspan:span varcharspan(span"spanpermalinkspan"span,span {span lengthspan:span 255span }span)span.spannotNullspan()span, spanspan parentIdspan:span integerspan(span"spanparent_idspan"span)span.spanreferencesspan(span():span anyspan =>span commentsspan.spanidspan,span { spanspan onDeletespan:span "spancascadespan"span, spanspan }span)span, spanspan createdAtspan:span timestampspan(span"spancreated_atspan"span,span {span withTimezonespan:span truespan }span)span.spandefaultNowspan()span, spanspan}span)span; spanspan spanspanexportspan typespan Commentspan =span typeofspan commentsspan.span$inferSelectspan; spanspanexportspan typespan NewCommentspan =span typeofspan commentsspan.span$inferInsertspan; h33.2 定义关系（relations）pstrongcodeserver/db/schema/relations.tsprecodespanspanimportspan {span relationsspan }span fromspan "spandrizzle-ormspan"span; spanspanimportspan {span usersspan }span fromspan "span./usersspan"span; spanspanimportspan {span commentsspan }span fromspan "span./commentsspan"span; spanspan spanspan// 评论 -> 用户（多对一） spanspanexportspan constspan commentsRelations span=span relationsspan(commentsspan,span ({span onespan })span =>span (span{ spanspan userspan:span onespan(usersspan,span { spanspan fieldsspan:span [commentsspan.spanuserId]span, spanspan referencesspan:span [usersspan.spanid]span, spanspan }span)span, spanspan}span))span; spanspan spanspan// 用户 -> 评论（一对多） spanspanexportspan constspan usersRelations span=span relationsspan(usersspan,span ({span manyspan })span =>span (span{ spanspan commentsspan:span manyspan(comments)span, spanspan}span))span; h33.3 统一导出pstrongcodeserver/db/schema/index.tsprecodespanspanexportspan *span fromspan "span./usersspan"span; spanspanexportspan *span fromspan "span./commentsspan"span; spanspanexportspan *span fromspan "span./relationsspan"span; hrh2🔌 第四步：创建数据库连接工具pNuxt 中，数据库连接应放在 codeserver/db/ 下以便导入。pstrongcodeserver/db.tsprecodespanspanimportspan {span drizzlespan }span fromspan "spandrizzle-orm/node-postgresspan"span; spanspanimportspan {span Poolspan }span fromspan "spanpgspan"span; spanspanimportspan *span asspan schema spanfromspan "span../db/schemaspan"span;span // 导入完整的 schema spanspan spanspanconstspan config span=span useRuntimeConfigspan()span; spanspan spanspanconstspan pool span=span newspan Poolspan(span{ spanspan connectionStringspan:span configspan.spandatabaseUrlspan, spanspan}span)span; spanspan spanspan// 导出函数，每次调用获取新连接（防止连接泄漏） spanspanexportspan constspan useDB span=span ()span =>span drizzlespan(poolspan,span {span schema span}span)span; blockquotepstrong注意：必须传入完整的 codeschema 对象（包含表和关系），否则无法使用 codewith 等关系查询。hrh2🧪 第五步：验证配置p创建测试 API 路由，确保一切正常。pstrongcodeserver/api/test/db.get.tsprecodespanspanimportspan {span sqlspan }span fromspan "spandrizzle-ormspan"span; spanspan spanspanexportspan defaultspan defineEventHandlerspan(spanasyncspan (spaneventspan)span =>span { spanspan tryspan { spanspan constspan dbspan =span useDBspan()span; spanspan constspan resultspan =span awaitspan dbspan.spanexecutespan(spansqlspan`spanSELECT 1+1 as resultspan`span)span; spanspan returnspan {span successspan:span truespan,span dataspan:span resultspan.spanrowsspan[span0span] span}; spanspan }span catchspan (spanerrorspan) span{ spanspan consolespan.spanerrorspan(span"spanDB connection failed:span"span,span errorspan)span; spanspan returnspan {span successspan:span falsespan,span errorspan:span Stringspan(spanerrorspan) span}; spanspan } spanspan}span)span; p访问 codehttp://localhost:3000/api/test/db，若返回 code{ result: 2 } 则连接成功。hrh2📜 第六步：数据库迁移h36.1 配置 codedrizzle.config.tsp在项目根目录创建：precodespanspanimportspan "spandotenv/configspan"span; spanspanimportspan {span defineConfigspan }span fromspan "spandrizzle-kitspan"span; spanspan spanspanexportspan defaultspan defineConfigspan(span{ spanspan outspan:span "span./server/db/migrationsspan"span, spanspan schemaspan:span "span./server/db/schema/index.tsspan"span,span // 指向统一导出文件 spanspan dialectspan:span "spanpostgresqlspan"span, spanspan dbCredentialsspan:span { spanspan urlspan:span processspan.spanenvspan.spanNUXT_DATABASE_URLspan!, spanspan }, spanspan}span)span; h36.2 生成迁移文件precodespanspannpxspan drizzle-kitspan generate p这会在 codeserver/db/migrations 生成 SQL 文件。h36.3 执行迁移precodespanspannpxspan drizzle-kitspan migrate pstrong开发环境也可以直接用 codepush 快速同步：precodespanspannpxspan drizzle-kitspan push blockquotepstrong生产环境：必须使用 codegenerate + codemigrate，并将生成的 SQL 文件纳入版本控制，以便回滚和审核。hrh2🛠️ 第七步：在 API 中使用 Drizzleh3查询示例（带关系）pstrongcodeserver/api/comments.get.tsprecodespanspanimportspan {span commentsspan }span fromspan "span~/server/db/schemaspan"span;span // 需要显式导入表定义 spanspanimportspan {span eqspan,span descspan }span fromspan "spandrizzle-ormspan"span; spanspan spanspanexportspan defaultspan defineEventHandlerspan(spanasyncspan (spaneventspan)span =>span { spanspan constspan queryspan =span getQueryspan(spaneventspan)span; spanspan constspan permalinkspan =span queryspan.spanpermalinkspan asspan stringspan; spanspan spanspan constspan dbspan =span useDBspan()span; spanspan constspan resultspan =span awaitspan dbspan.spanqueryspan.spancommentsspan.spanfindManyspan(span{ spanspan wherespan:span eqspan(spancommentsspan.spanpermalinkspan,span permalinkspan)span, spanspan orderByspan:span [spandescspan(spancommentsspan.spancreatedAtspan)]span, spanspan withspan:span { spanspan userspan:span { spanspan columnsspan:span {span usernamespan:span truespan }, spanspan }, spanspan }, spanspan }span)span; spanspan spanspan returnspan {span successspan:span truespan,span dataspan:span resultspan }; spanspan}span)span; h3插入示例pstrongcodeserver/api/comments.post.tsprecodespanspanimportspan {span commentsspan,span typespan NewCommentspan }span fromspan "span~/server/db/schemaspan"span; spanspanimportspan {span useDBspan }span fromspan "span~~/server/dbspan"span; spanspan spanspanexportspan defaultspan defineEventHandlerspan(spanasyncspan (spaneventspan)span =>span { spanspan constspan bodyspan =span awaitspan readBodyspan(spaneventspan)span; spanspan constspan dbspan =span useDBspan()span; spanspan spanspan constspan newCommentspan:span NewCommentspan =span { spanspan userIdspan:span bodyspan.spanuserIdspan, spanspan contentspan:span bodyspan.spancontentspan, spanspan permalinkspan:span bodyspan.spanpermalinkspan, spanspan }; spanspan spanspan constspan [spaninsertedspan]span =span awaitspan dbspan.spaninsertspan(spancommentsspan)span.spanvaluesspan(spannewCommentspan)span.spanreturningspan()span; spanspan returnspan {span successspan:span truespan,span dataspan:span insertedspan }; spanspan}span)span; hrh2🐛 常见错误与解决方案h3错误 1：codeCannot read properties of undefined (reading 'referencedTable')pstrong原因：初始化 codedrizzle 时没有传入完整 schema（缺少 relations）。 strong解决：确保 codeuseDB() 中 codedrizzle(pool, { schema }) 的 codeschema 对象包含了 coderelations 导出。h3错误 2：codeuseDB() 未定义pstrong原因：文件未放在 codeserver/utils/ 下，或 Nuxt 自动导入失效（需重启 dev）。 strong解决：检查文件路径，重启 codepnpm dev。h3错误 3：codedb.query.comments.findMany 不存在pstrong原因：没有启用 Drizzle 的关系查询 API，需要传入 schema 并确保 codedrizzle-orm 版本支持。 strong解决：确认 codeuseDB() 返回的是带有 codequery 属性的实例（即传入了 schema）。h3错误 4：迁移时找不到表pstrong原因：codedrizzle.config.ts 中的 codeschema 路径错误，或指向的文件没有导出所有表。 strong解决：确保路径正确，且 codeschema/index.ts 导出了所有表。h3错误 5：生产环境数据库连接失败pstrong原因：环境变量未正确设置，或连接字符串格式错误。 strong解决：在服务器上检查 codeNUXT_DATABASE_URL 是否正确，并确保网络可达。hrh2💡 最佳实践总结ollistrong永远不要提交 code.env。listrongschema 必须包含 relations，否则无法使用 codewith 查询。listrong数据库连接函数放在 codeserver/utils/，利用 Nuxt 自动导入。listrong在 API 中显式导入表定义（如 codeimport { users } from '~/server/db/schema'）。listrong生产环境使用迁移文件，禁止用 codepush。listrong测试环境与开发环境分离，用不同的数据库 URL。hrh2🎯 最终验证p完成以上步骤后，你应该能够：ulli通过 codepnpm dev 启动项目，访问测试 API 得到 code{ result: 2 }li使用 codedrizzle-kit generate/migrate 管理数据库变更li在 API 中正确查询带关联的数据li在生产环境中通过环境变量连接数据库p如果遇到任何问题，请对照每一步仔细检查。记住：strongDrizzle 的官方文档是通用指南，Nuxt 集成需要根据其目录结构和自动导入机制进行调整。这篇教程已为你铺平道路，祝你顺利！stylehtml pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}