NestJS v11 + Prisma v7 ESM 迁移配置解析

NestJS v11 CommonJS to ESM

首先是 NestJS v11 确实是支持了 ESM,但并非默认就是 ESM。

package.json --- 声明 ESM 模块

js 复制代码
{
  "type": "module"   // 关键:声明为 ESM 包
}

tsconfig.json --- 编译目标

json 复制代码
{
  "compilerOptions": {
    "target": "ES2022",           // 编译目标
    "module": "ESNext",           // 从 CommonJS 改为 ESNext
    "moduleResolution": "bundler" // 从 node 改为 bundler(重要!)
  }
}

为何 moduleResolution: "bundler" 是必须的?

  • Node.js 的 node 模式不支持 package.json 中的 exports 字段条件导出
  • ESM 要求使用 import 而非 require(),bundler 模式能正确解析这类路径

这里实际还有一个重要问题,如果 moduleResolution 属性不为 "bundler " 那么 import 这里的导入的文件后缀一定要明确写是 .ts or .js。不然实际进行构建还是会报错。

配置变化

diff 复制代码
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
-   "module": "CommonJS",
-   "moduleResolution": "node",
+   "target": "ES2022",
+   "module": "ESNext",
+   "moduleResolution": "bundler",
    "baseUrl": ".",
    "outDir": "dist",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
+   "esModuleInterop": true,
+   "allowSyntheticDefaultImports": true,
+   "strict": true,
+   "skipLibCheck": true,
    "paths": {
      "@/*": ["src/*"]
-   }
+   },
  }

Prisma v5 到 v7 升级

Prisma v7 最大的变化是数据源配置方式

在 v5 版本,数据源 URL 是写在 schema.prisma 文件里的

go 复制代码
datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

这种方式简单直接,但有个问题------不同环境(开发、测试、生产)需要不同的数据库 URL,要么靠环境变量展开,要么靠 CI 脚本动态修改 schema.prisma,都不够灵活。

到了 v7,Prisma 团队决定把这个职责剥离出来:数据源配置不再放在 schema 文件里,而是通过独立的 prisma.config.ts 来管理

ts 复制代码
// prisma.config.ts
import 'dotenv/config'
import type { PrismaConfig } from 'prisma'

export default {
  schema: 'prisma/schema.prisma',
  datasource: {
    url: process.env.DATABASE_URL || 'file:./dev/dev.db',
  },
}

注意:prisma.config.ts 需要放在根路径

这个文件本质上是 Prisma 的全局配置文件,它在 prisma generate 和运行时都会被读取。由于它是一个普通的 TypeScript 文件,你可以:

  1. dotenv 加载环境变量
  2. 根据条件动态决定 URL(比如连接池数量、超时设置)
  3. 在同一个文件里做更多 Prisma 相关的全局配置

与此同时,v7 引入了一个新概念------适配器(Adapter) 。以前 Prisma 的 SQLite 支持是通过内置逻辑实现的,现在则统一走 @prisma/adapter-libsql,底层调用 libsql-client。这让 Prisma 的数据库支持更趋于插件化,理论上未来可以接入更多数据库。

所以升级的实质是:把原来硬编码在 schema 里的数据源 URL 拿出来,放到一个独立的、可以用 TypeScript 逻辑控制的配置文件里,然后通过适配器来建立真正的数据库连接。

依赖变化

diff 复制代码
- "@prisma/client": "^5.12.0"
+ "@prisma/client": "^7.8.0"
+ "@prisma/adapter-libsql": "^7.8.0"
+ "@libsql/client": "^0.17.4"
- "prisma": "^5.12.0"
+ "prisma": "^7.8.0"

关键变更对照(v5 : v7)

关键属性 early access (v6.4+) v7 正式
earlyAccess 必写 已删除
schema 写法 path.join(...) 自由拼 支持相对路径字符串或 path.join
migrate.adapter 已删 ,改 experimental.adapter 或直接 adapter()
engine / directUrl 已删 ,回 schema.prismagenerator
.env 自动加载 ,必须 import 'dotenv/config'
env() 来源 --- prisma/config 导出

NestJs + Prisma

实现 prisma.service

ts 复制代码
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'
import { PrismaLibSql } from '@prisma/adapter-libsql'

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  constructor() {
    // 关键:使用 libsql 适配器创建 PrismaClient
    const adapter = new PrismaLibSql({
      url: process.env.DATABASE_URL || 'file:./dev.db',
    })
    super({ adapter })  // 通过 adapter 选项注入
  }

  async onModuleInit() {
    await this.$connect()
  }

  async onModuleDestroy() {
    await this.$disconnect()
  }
}

注册到 NestJS 全局

common.module.ts

ts 复制代码
@Global()  // 全局模块,所有模块都能 inject PrismaService
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class CommonModule {}

然后在 app.module.ts 导入

ts 复制代码
@Module({
  imports: [ConfigModule.forRoot(), CommonModule, ...],
})
export class AppModule {}

关键变化点

旧版 (Prisma v5) 新版 (Prisma v7)
new PrismaClient() new PrismaClient({ adapter })
直接传 URL 或靠环境变量 通过 PrismaLibSql 适配器
无需手动调用 $connect 依赖 OnModuleInit 生命周期

新增依赖

json 复制代码
"tsx": "^4.23.0"           // ESM 友好的 TS 执行器
"dotenv": "^17.4.2"        // 环境变量加载
"@nestjs/config": "^4.0.4" // NestJS 配置模块

启动方式变更

diff 复制代码
- "dev": "nest start --watch"
+ "dev": "tsx watch src/main.ts"

原因

  • nest start --watch 在 ESM 模式下存在问题
  • tsx 是支持 ESM 的 TypeScript 执行器,比 ts-node 更适合 ESM

当然使用 ts-node 也可以,这样可以保持原来的 nest start 方式,但是实际上重写的改动量会比 tsc 大。

常见问题排查

症状 解决方案
Cannot use import outside module 确认 package.json"type": "module"
Module not found: @prisma/client 先运行 prisma generate
ExperimentalWarning: --experimental-loader 使用 tsx 而非 Node 原生加载
Prisma URL 配置不生效确认 prisma.config.ts 存在且路径正确
相关推荐
IT_陈寒1 小时前
React的setState竟然不是立刻生效的,害我调试半天
前端·人工智能·后端
简简单单就是我_hehe1 小时前
前端埋点日志解决方案:完整 User-Agent 解析工具实现,爬虫识别、设备品牌、操作系统、浏览器版本全解析(附完整源码)
前端·爬虫
William_Xu2 小时前
Vue 2 与 Vue 3 生命周期对比
前端
Hilaku2 小时前
为什么你的大文件断点续传,一遇到弱网就崩溃?
前端·javascript·程序员
逝水无殇2 小时前
C# 字符串(String)详解
开发语言·后端·c#
神奇小汤圆3 小时前
牛客网Java八股文|2026秋招面试题汇总(金九银十冲刺专用)
后端
cfm_29143 小时前
SpringBoot MockMvc
spring boot·后端·log4j
胡哈3 小时前
React Compiler:让开发者忘掉 memo 这件事
前端·react.js
神奇小汤圆3 小时前
你的Java天花板,不是Spring Boot,是思维方式
后端