Nest 与 Prisma 结合
Prisma 篇
注意
- 我这里用的版本合集
- prisma10.2.0 版本
- "@prisma/client": "^10.2.0",
- "@prisma/extension-read-replicas": "^0.5.0",
这里它会比老版本多了一个 prisma.config.ts 文件
这里环境变量只能用 env 文件形式 yml 文件形式不行
新建一个 Nest 项目
- 具体的参考前面章节
安装 Prisma
- 安装 Prisma CLI
bash
// 基础的使用包
pnpm install prisma@7.10
// 这个时候 他会提示 [ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: @prisma/engines@7.10.0, prisma@7.10.0
// 运行 pnpm approve-builds 然后输入 a 就行
// 扩展包
pnpm install @prisma/client
// 读写分离
pnpm install @prisma/extension-read-replicas
// 数据库链接
pnpm install @prisma/adapter-mariadb初始化 Prisma
bash
npx prisma init- 这个时候无论你
.env里面写了什么它都会给你覆盖
修改.env文件
这里我使用的是 mysql 数据库
修改
.env,.env.development,.env.production文件
bash
DATABASE_URL='mysql://用户名:密码@地址:端口号/数据库名'
DATABASE_USER="用户名"
DATABASE_PASSWORD="密码"
DATABASE_NAME="数据库名"
DATABASE_HOST="链接地址"
DATABASE_PORT=端口号
DATABASE_READ_URL= 'mysql://用户名:密码@地址:端口号/数据库名'
DATABASE_READ_USER="用户名"
DATABASE_READ_PASSWORD="密码"
DATABASE_READ_NAME="数据库名"
DATABASE_READ_HOST="链接地址"
DATABASE_READ_PORT=端口号
- 修改 prisma/schema.prisma 文件
ts
generator client {
provider = "prisma-client"
output = "../prisma/generated/prisma"
moduleFormat = "cjs"
}
datasource db {
provider = "mysql"
}修改 prisma7.config.ts 文件
ts
import { defineConfig, env } from "prisma/config";
import "dotenv/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: env("DATABASE_URL"),
},
});拉取数据库
bash
npx prisma db pull- 这个时候 你的 prisma/schema.prisma 文件应该会自动生成数据库的表结构
多文件处理
- 在根目录下面找到
prisma文件夹
依次按照schema.prisma文件里面的内容,新建对应的文件 类似
ts
prisma
├── schema
│ ├── schema.prisma
│ ├── app.prisma
│ ├── shop.prisma
│ └── erp.prisma- 每一个
prisma文件 对应的就是一张表
分出去以后 把schema.prisma 里面表的结构都删除仅保留开始的.
- 把
schema.prisma放到prisma/schema文件夹下
再次修改schema.prisma 文件
ts
generator client {
provider = "prisma-client"
output = "../generated/prisma"
moduleFormat = "cjs"
}
datasource db {
provider = "mysql"
}再次修改
- 把prisma文件夹放到 src 目录下面
ts
src
├── prisma
├──generated
├──schema
├── schema.prisma
├── app.prisma
├── shop.prisma
└── erp.prisma- prisma.config.ts 文件
ts
import { defineConfig, env } from "prisma/config";
import "dotenv/config";
export default defineConfig({
schema: "./src/prisma/schema",
migrations: {
path: "./src/prisma/migrations",
},
datasource: {
url: env("DATABASE_URL"),
},
});生成 Prisma Client
ts
npx prisma generateNest 篇
项目结构
- 在src目录下面新建一个prisma文件夹
bash
common
├──prismaModule
├── prisma.extension.service.ts // prisma 扩展
├── prisma.module.ts // prisma 模块
├── prisma.service.ts // prisma 暴露出去的服务
├── prisma.provider.ts // 核心prisma 提供创建 一个 Module 和 Service
- 名字任意 我这里起名就是 prisma.module.ts 和 prisma.service.ts
prisma.extension.service.ts
- 给 prisma 添加一个扩展方法 exists
ts
// query-helper.service.ts
import { Injectable } from "@nestjs/common";
import { Prisma } from "@prisma/client/extension.js";
@Injectable()
export class PrismaQueryHelperService {
//any logic
existsExtension = Prisma.defineExtension({
name: "exists-extension",
model: {
$allModels: {
async exists<T>(
this: T,
where: Prisma.Args<T, "findFirst">["where"],
): Promise<boolean> {
const context = Prisma.getExtensionContext(this);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = await (context as any).findFirst({ where });
return result !== null;
},
},
},
});
}prisma.extension2.service.ts
- 自己写的分页方法
ts
import { Injectable } from "@nestjs/common";
import { Prisma } from "@prisma/client/extension.js";
@Injectable()
export class PrismaQueryHelperService2 {
existsExtension = Prisma.defineExtension({
name: "extension2",
model: {
$allModels: {
async paginate<T>(
this: T,
page: number,
pageSize: number,
options: any = {},
): Promise<any> {
const context = Prisma.getExtensionContext(this);
const skip = (page - 1) * pageSize;
const [data, total] = await Promise.all([
(context as any).findMany({
skip,
take: pageSize,
...options,
}),
(context as any).count({ where: options.where }),
]);
return {
data,
pagination: {
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize),
hasNextPage: page < Math.ceil(total / pageSize),
hasPreviousPage: page > 1,
},
};
},
},
},
});
}prisma.provider.ts
ts
import { Injectable, OnModuleDestroy, OnModuleInit } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { PrismaClient } from "../../prisma/generated/prisma/client.js";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import { PrismaQueryHelperService } from "./prisma.extension.service.js";
import { PrismaQueryHelperService2 } from "./prisma.extension2.service.js";
import { readReplicas } from "@prisma/extension-read-replicas";
@Injectable()
export class PrismaProvider
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
constructor(
private readonly configService: ConfigService,
private readonly prismaQueryHelperService: PrismaQueryHelperService,
private readonly prismaQueryHelperService2: PrismaQueryHelperService2,
) {
// 构造函数执行时,ConfigModule 已加载完 env 文件
super({
adapter: new PrismaMariaDb({
host: configService.getOrThrow<string>("DATABASE_HOST"),
user: configService.getOrThrow<string>("DATABASE_USER"),
password: configService.getOrThrow<string>("DATABASE_PASSWORD"),
database: configService.getOrThrow<string>("DATABASE_NAME"),
allowPublicKeyRetrieval: true, // 允许客户端向服务端请求 RSA 公钥
}),
log: ["info", "query"],
});
}
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
withExtensions() {
const readReplicasClient = new PrismaClient({
adapter: new PrismaMariaDb({
host: this.configService.getOrThrow<string>("DATABASE_READ_HOST"),
user: this.configService.getOrThrow<string>("DATABASE_READ_USER"),
password: this.configService.getOrThrow<string>(
"DATABASE_READ_PASSWORD",
),
database: this.configService.getOrThrow<string>("DATABASE_READ_NAME"),
allowPublicKeyRetrieval: true, // 允许客户端向服务端请求 RSA 公钥
}),
log: ["info", "query"],
});
return this.$extends(this.prismaQueryHelperService.existsExtension)
.$extends(readReplicas({ replicas: [readReplicasClient] }))
.$extends(this.prismaQueryHelperService2.existsExtension);
}
}prisma.service.ts
ts
import { Inject, Injectable, OnModuleInit, Type } from "@nestjs/common";
import { PrismaProvider } from "./prisma.provider";
const ExtendedPrismaClient = class {
constructor(provider: PrismaProvider) {
return provider.withExtensions();
}
} as Type<ReturnType<PrismaProvider["withExtensions"]>>;
@Injectable()
export class PrismadbService extends ExtendedPrismaClient {
constructor(provider: PrismaProvider) {
super(provider);
}
}prisma.module.ts
ts
import { PrismadbService } from "./prisma.service"; // 服务
import { Module, Global } from "@nestjs/common";
import { PrismaProvider } from "./prisma.provider"; // 提供
import { PrismaQueryHelperService } from "./prisma.extension.service"; // 扩展
import { PrismaQueryHelperService2 } from "./prisma.extension2.service"; // 扩展
@Global() // Global decorator to make this module available globally
@Module({
imports: [],
controllers: [],
providers: [
PrismadbService,
PrismaProvider,
PrismaQueryHelperService,
PrismaQueryHelperService2,
],
exports: [
PrismadbService,
PrismaProvider,
PrismaQueryHelperService,
PrismaQueryHelperService2,
],
})
export class PrismadbModule {}使用
app.module.ts
- 重点引入 PrismadbModule 模块
ts
import { Module } from "@nestjs/common";
// 配置文件
import { ConfigModule } from "@nestjs/config";
import * as dotenv from "dotenv";
import { PrismadbModule } from "./common/prismaModule/prisma.module.js";
import { AppController } from "./app.controller.js";
import { AppService } from "./app.service.js";
const envPath = `.env.${process.env.NODE_ENV || "development"}`;
@Module({
imports: [
PrismadbModule,
// 修改配置
ConfigModule.forRoot({
isGlobal: true,
envFilePath: envPath,
// 这里新增.env的文件解析
load: [() => dotenv.config({ path: ".env" })],
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}控制器
ts
import { Controller, Get, Post, Body } from "@nestjs/common";
import { AppService } from "./app.service.js";
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Post("getalluser")
async getAllUser() {
return this.appService.getAllUsers();
}
@Post("getpageduser")
async getPagedUser(@Body() body: any) {
return this.appService.getPagedUsers(body.page, body.pageSize);
}
}服务app.service.ts
ts
import { Injectable } from "@nestjs/common";
import { PrismadbService } from "./common/prismaModule/prisma.service.js";
@Injectable()
export class AppService {
constructor(private readonly prismaService: PrismadbService) {}
getHello(): string {
return "Hello World!";
}
// 获取所有用户列表
async getAllUsers() {
const result = await this.prismaService.admin.findMany();
return result.map((item) => ({
...item,
id: item.id.toString(),
}));
}
// 获取分页列表
async getPagedUsers(page: number, pageSize: number) {
const current_page = page || 1; // 当前页
const page_size = pageSize || 10; // 每页条数
const result = await this.prismaService.student.paginate(
current_page,
page_size,
{
where: { id: { gte: 2 } },
orderBy: { id: "desc" },
},
);
// 处理 BigInt 序列化
if (result.data && Array.isArray(result.data)) {
result.data = result.data.map((item: any) => ({
...item,
id: item.id?.toString(),
}));
}
return result;
}
}调用
ts
POST http://localhost:5000/getalluser
Content-Type: application/json
POST http://localhost:5000/getpageduser
Content-Type: application/json
{
"pageNum": 1,
"pageSize": 10
}