概述
本节开始构建自定义的 PrismaModule,目标是实现与 TypeORM、Mongoose 官方模块类似的动态模块结构。通过学习官方模块的代码组织方式,建立 PrismaCoreModule + PrismaModule 的双层架构。
官方模块代码组织分析
目录结构对比
以 @nestjs/typeorm 和 @nestjs/mongoose 为参考,官方模块普遍采用以下结构:
lib/
├── interfaces/ # 接口定义(TypeScript 类型)
│ ├── typeorm-options.interface.ts
│ └── typeorm-module-async-options.interface.ts
├── common/ # 公共工具(装饰器、辅助函数)
│ ├── decorators/
│ └── utils/
├── typeorm-core.module.ts # 核心逻辑(200+ 行)
├── typeorm.module.ts # 模块入口(~50 行)
└── index.ts # 统一导出
text
命名规范
NestJS 项目中的文件命名采用 kebab-case(小写 + 短横线),不使用 camelCase 或 PascalCase:
typeorm-core.module.ts(正确)TypeOrmCoreModule.ts(不推荐)
双层模块架构
| 文件 | 职责 | 代码量 |
|---|---|---|
typeorm.module.ts | 提供对外的 API(forRoot、forRootAsync、forFeature) | ~50 行 |
typeorm-core.module.ts | 核心逻辑(Provider 创建、连接管理、生命周期钩子) | 200+ 行 |
双层架构的优势:
- 接口统一 -- 外层模块提供一致的 API 形式
- 逻辑隔离 -- 核心逻辑变更只需修改 Core Module,不影响入口文件
- 快速定位 -- 团队成员可以迅速找到逻辑代码所在
PrismaModule 结构搭建
定义接口
// prisma/prisma-options.interface.ts
import { Prisma, PrismaClient } from '@prisma/client';
export interface PrismaModuleOptions {
url?: string;
options?: Prisma.PrismaClientOptions;
}
typescript
接口包含两种传参方式:
url-- 数据库连接字符串options-- Prisma Client 的完整配置选项
创建 Core Module
// prisma/prisma-core.module.ts
import { Global, Module, DynamicModule, Provider } from '@nestjs/common';
import { OnApplicationShutdown } from '@nestjs/common';
@Global()
@Module({})
export class PrismaCoreModule implements OnApplicationShutdown {
static forRoot(options: PrismaModuleOptions): DynamicModule {
const prismaClientProvider: Provider = {
provide: PrismaClient,
useFactory: () => {
return new PrismaClient({
datasources: {
db: {
url: options.url,
},
},
...options.options,
});
},
};
return {
module: PrismaCoreModule,
providers: [prismaClientProvider],
exports: [prismaClientProvider],
};
}
async onApplicationShutdown() {
// 清理连接
}
}
typescript
创建入口模块
// prisma/prisma.module.ts
import { DynamicModule } from '@nestjs/common';
import { PrismaCoreModule } from './prisma-core.module';
import { PrismaModuleOptions } from './prisma-options.interface';
export class PrismaModule {
static forRoot(options: PrismaModuleOptions): DynamicModule {
return {
module: PrismaModule,
imports: [PrismaCoreModule.forRoot(options)],
};
}
}
typescript
入口模块仅负责透传 options,所有核心逻辑都在 Core Module 中。
providers 与 exports 的关系
providers: [PrismaClient] -- 在 DI 系统中注册(初始化实例)
exports: [PrismaClient] -- 允许其他模块注入使用
如果只写在 providers 中,实例会被创建但其他模块无法访问。
通常 providers 和 exports 成对出现。
text
TypeScript 方法重载
forRoot 需要支持多种参数形式,通过 TypeScript 的方法重载实现:
// prisma/prisma.module.ts
export class PrismaModule {
// 重载签名 1:接收 options 对象
static forRoot(options: PrismaModuleOptions): DynamicModule;
// 重载签名 2:接收 URL 字符串
static forRoot(url: string): DynamicModule;
// 实际实现
static forRoot(arg: PrismaModuleOptions | string): DynamicModule {
let options: PrismaModuleOptions;
if (typeof arg === 'string') {
options = { url: arg };
} else {
options = arg;
}
return {
module: PrismaModule,
imports: [PrismaCoreModule.forRoot(options)],
};
}
}
typescript
在 AppModule 中使用
// app.module.ts
import { PrismaModule } from './prisma/prisma.module';
@Module({
imports: [
// 方式一:直接传 URL 字符串
PrismaModule.forRoot('mysql://root:password@localhost:3306/test_db'),
// 方式二:传 options 对象
// PrismaModule.forRoot({
// url: 'mysql://root:password@localhost:3306/test_db',
// options: { log: ['query'] },
// }),
],
})
export class AppModule {}
typescript
Prisma Client 初始化问题
generate 与 provider 的绑定
Prisma Client 通过 npx prisma generate 根据_schema.prisma_ 文件中的 provider 字段生成对应数据库的客户端代码:
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql" // 决定生成的 Client 类型
url = env("DATABASE_URL")
}
prisma
切换数据库时的错误
当 schema.prisma 中的 provider 为 mysql 时,运行 npx prisma generate 生成的 Client 只支持 MySQL 连接。如果代码中使用 PostgreSQL 的 URL 连接,会报错:
PrismaClient initialization error:
Database URL must start with `postgresql://`
text
解决方案: 切换数据库类型后,需要修改 schema.prisma 中的 provider 和 url,然后重新运行 npx prisma generate。这个限制与 TypeORM 的外置驱动模式不同,是 Prisma 的特殊之处。
关键要点
- 双层架构 -- Module 负责对外 API,Core Module 负责核心逻辑
- 方法重载 -- TypeScript 支持通过多个签名实现函数重载
- Provider/Exports 配对 -- 确保依赖注入系统正确注册和暴露实例
- Prisma generate 限制 -- Client 类型与 schema.prisma 中的 provider 强绑定,动态切换数据库时需特别注意
↑