首页 / 文章

在 TypeScript Node.js 项目中使用 PostgreSQL 部署 Prisma 7

修复 TypeScript 中常见的 Prisma 7 配置错误,包括字符串或未定义的 URL 问题以及 rootDir 相关故障,并使用 pg 驱动适配器连接 PostgreSQL。

发布者:REACTAPP.TOP阅读约 7 分钟1666 词

来源: I Wasted 4 Hours Setting Up Prisma — Here’s How You Should Set Up Prisma v7

在普通的 TypeScript Node.js 项目中按照 Prisma 快速入门指南操作时,往往还没执行任何查询就会遇到大量编译错误。问题并不出在模型本身,而在于 TypeScript 编译器、Prisma 的新配置文件以及生成的客户端是如何与传统的 src/dist 目录结构相互作用的。本指南将解释你最可能遇到的三种错误、每种错误产生的原因,以及一套能顺利编译且基于 PostgreSQL 的逐步设置方法。Prisma 7 修改了多项默认设置,因此请查阅最新的 Prisma 文档,了解自本指南编写以来可能发生的变化。

导致新项目设置失败的三种错误

连接 URL 被输入为字符串或未定义

在 prisma.config.ts 中,数据库地址通常通过 process.env.DATABASE_URL 来获取。TypeScript 将每个环境变量定义为 string | undefined 类型,因为无法保证其在运行时已被设置。而配置文件期望的是普通的 string 类型,因此编译器会拒绝该赋值操作。

位于根目录外的 prisma.config.ts

一个典型的 TypeScript 项目会将 rootDir 设置为 src,outDir 设置为 dist。Prisma 会在项目根目录下生成 prisma.config.ts 文件,默认情况下编译器会包含它找到的所有 .ts 文件。随后它会报错,称有位于 rootDir 之外的文件也被纳入了程序中。将配置文件移到 src 目录可以消除该错误,但这并非正确的解决方法:src 是用于存放路由、控制器和数据访问等应用代码的,而非工具配置。

根目录之外的生成客户端

生成的客户端也会出现同样的问题。如果 Prisma 将其写入项目根目录下的 generated/prisma,编译器又会发现 rootDir 外存在 TypeScript 文件。将生成的代码移到 src 目录也不是明智之举,因为这些是来自 prisma generate 的构建输出,而非需要维护的源代码。

若不解决这些问题,项目将无法启动。另一个同样令人烦恼的副作用是:prisma.config.ts 的编译后的 JavaScript 文件可能会被保存在 dist 目录之外,从而让代码库变得杂乱无章。逐个修复错误往往还会引发新的问题,因此最好整体改进配置。

第一步:创建项目并安装依赖项

从一个空的 TypeScript 项目开始。安装编译器、用于直接运行 TypeScript 的 tsx,以及 Node 的类型定义,然后生成 tsconfig.json 文件:

npm init
npm install typescript tsx @types/node --save-dev
npx tsc --init

接下来,安装 Prisma。CLI 和 pg 类型定义属于开发依赖项;而客户端、PostgreSQL 驱动适配器、pg 驱动以及 dotenv 则属于运行时依赖项:

npm install prisma @types/pg --save-dev
npm install @prisma/client @prisma/adapter-pg pg dotenv

这里 @prisma/adapter-pg 包非常重要。在 Prisma 7 中,客户端通过 JavaScript 驱动适配器与数据库通信,因此需要传入已配置好的 pg 连接信息,而不能仅依赖架构文件中的 URL。

步骤 2:配置 ES 模块及编译器布局

在 package.json 中将该包标记为 ES 模块:

{
  "type": "module"
}

接着在 tsconfig.json 中设置源代码目录和输出目录,然后创建 src 文件夹:

"rootDir": "./src",
"outDir": "./dist",

步骤 3:初始化 Prisma

运行一次 CLI 命令(单独运行时只会显示可用命令并确认安装正常):

npx prisma

然后为 PostgreSQL 初始化 Prisma:

npx prisma init --datasource-provider postgresql --output ../generated/prisma

这样会生成 prisma.config.ts 文件、包含 schema.prisma 的 prisma 文件夹,以及用于存储数据库 URL 的 .env 文件。--output 参数用于指定客户端代码的生成位置;步骤 4 会再次确认该选择。就在这一步之后,就会出现上述错误。

步骤 4:修复配置

通过三项针对性修改即可解决所有错误。

使用 Prisma 的 env 工具读取 URL

请将prisma.config.ts中的内容替换为以下内容。尽管代码片段中另有标注,但该文件实际上是用TypeScript编写的。

import "dotenv/config";
import type { PrismaConfig } from "prisma";
import { env } from "prisma/config";

export default {
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
    seed: "tsx prisma/seed.ts",
  },
  datasource: {
    url: env("DATABASE_URL"),
  },
} satisfies PrismaConfig;

需要注意的事项:

  • import "dotenv/config"会在任何代码读取之前加载.env文件。在Prisma 7的这种配置下,它不会再自动加载该文件。
  • prisma/config中的env("DATABASE_URL")方法会返回一个string类型,如果该变量不存在则会抛出明显错误,从而避免了string | undefined类型的错误。
  • satisfies PrismaConfig会对对象进行类型检查,而不会将其类型扩大。
  • 该配置还指定了模式文件和迁移文件的存放位置,以及用于初始化数据库的命令。

限制编译器包含的内容

在 tsconfig.json 的末尾添加一个 include 条目:

"include": ["src"],

这样就能告诉编译器仅编译 src 目录下的文件。Prisma CLI 仍会单独读取 prisma.config.ts,但 tsc 不会再将其视为程序的一部分,因此 rootDir 错误就会消失,且不会有多余的 JavaScript 文件生成在 dist 目录之外。

将客户端代码生成到 node_modules 中

最后,编辑 schema.prisma,让生成器没有自定义的输出路径:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"

如果没有指定output,prisma-client-js生成器会将客户端代码写入node_modules目录,因此项目根目录中不会留下任何生成的内容,之前的rootDir错误也会随之消失。代码片段在数据源provider之后结束,需用大括号闭合该代码块。在Prisma 7中,连接URL位于prisma.config.ts文件中,而非模式文件中。

需要注意的是,这是一种权衡。Prisma较新的prisma-client生成器是以明确的输出目录为设计基础的,而prisma-client-js则是较旧的生成器。如果选择使用较新的版本,需将其输出目录置于src之外,并通过include选项将其排除在编译范围之外,如上所述。

第5步:添加数据库URL并执行迁移

将连接字符串放入 .env 文件中。下面的内容仅为占位符,请使用自己的数据库,并且绝不要提交真实值:

DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

在 schema.prisma 中定义模型,然后创建并应用第一个迁移:

npx prisma migrate dev --name init

第6步:生成客户端

根据架构文件生成带类型的客户端:

npx prisma generate

每当架构发生变化时都需再次运行此操作,以确保类型与模型保持同步。

第7步:创建共享的 Prisma 实例

创建 src/lib/prisma.ts 文件,为整个应用程序保存一个客户端实例。该文件会根据连接字符串构建 PostgreSQL 适配器,并将其传递给 PrismaClient。这段代码也是用 TypeScript 编写的:

import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "../../node_modules/@prisma/client/index.js";

const connectionString = `${process.env.DATABASE_URL}`;

const adapter = new PrismaPg({ connectionString });
const prisma = new PrismaClient({ adapter });

export default prisma;

客户端通过明确的相对路径从 node_modules 中导入:

import { PrismaClient } from "../../node_modules/@prisma/client/index.js";

该路径有效是因为在步骤4中客户端已被生成到node_modules目录中。在大多数配置下,直接使用@prisma/client这个标识符也能指向同一个包,因此应先尝试这种方式;只有当模块解析设置要求时才使用显式路径。

这段代码中的一个隐患是:将process.env.DATABASE_URL放在模板字符串中会导致缺失值被转换为字面文本"undefined",进而引发令人困惑的连接错误。若能在变量不存在时立即抛出异常,就能让问题一目了然。

现在,在需要访问数据库的任何地方,比如控制器或服务类中导入这个客户端实例,然后通过它来执行CRUD操作。使用一个共享的客户端可以避免为每个文件创建新的连接池。

关键要点

  • 在 TypeScript 中出现的绝大多数 Prisma 7 配置错误都源于编译器的 rootDir 规则,而非 Prisma 本身。
  • 使用 prisma/config 中的 env() 函数结合 dotenv/config,即可获得类型正确且已明确加载的数据库 URL。
  • 通过 "include": ["src"] 限制 tsc 的处理范围,这样工具文件和生成的代码就不会被纳入构建过程。
  • 将生成的客户端代码放在 node_modules 目录或被排除在外的输出文件夹中,切勿放入 src 目录。
  • 创建一个使用 PrismaPg 适配器的共享 PrismaClient,并在启动时验证连接字符串。

如果您使用的是 Next.js 而非普通的 Node.js 服务,我们的在 Next.js 16 TypeScript 项目中配置 Prisma 7指南涵盖了该框架特有的相关细节。