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

76 阅读4分钟

NestJS v11 CommonJS to ESM

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

package.json — 声明 ESM 模块

{
  "type": "module"   // 关键:声明为 ESM 包
}

tsconfig.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。不然实际进行构建还是会报错。

配置变化

{
  "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 文件里的

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

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

到了 v7,Prisma 团队决定把这个职责剥离出来:数据源配置不再放在 schema 文件里,而是通过独立的 prisma.config.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 逻辑控制的配置文件里,然后通过适配器来建立真正的数据库连接。

依赖变化

- "@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

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

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

然后在 app.module.ts 导入

@Module({
  imports: [ConfigModule.forRoot(), CommonModule, ...],
})
export class AppModule {}

关键变化点

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

新增依赖

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

启动方式变更

- "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 存在且路径正确

版权声明:
本文版权属于作者 林小帅,未经授权不得转载及二次修改。
转载或合作请在下方留言及联系方式。