Skip to content

Nest 与 Prisma 结合

Prisma 篇

注意

我这里用的 prisma6.16.3 版本

新建一个 Nest 项目

  • 具体的参考前面章节

安装 Prisma

  • 安装 Prisma CLI
bash
npm install -g prisma

初始化 Prisma

bash
npx prisma init

修改.env文件

bash
 DATABASE_URL='mysql://用户名:密码@地址:端口号/数据库名',
  • 修改 prisma/schema.prisma 文件
ts
generator client {
  provider = "prisma-client-js"
  output   = "../generated/prisma"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

拉取数据库

bash
npx prisma db pull
  • 这个时候 你的 prisma/schema.prisma 文件应该会自动生成数据库的表结构

在 package.json 里面添加脚本

ts
"prisma": {
    "schema": "prisma/schema"
  }

多文件处理

  • 在根目录下面找到prisma文件夹

依次按照schema.prisma文件里面的内容,新建对应的文件 类似

ts
prisma
├── schema
│   ├── schema.prisma
│   ├── app.prisma
│   ├── shop.prisma
│   └── erp.prisma
  • 每一个 prisma 文件 对应的就是一张表

schema.prisma 放到 prisma 文件夹下

生成 Prisma Client

ts
npx prisma generate

最后的结果

ts
prisma
├── generated
├── schema
│   ├── schema.prisma
│   ├── app.prisma
│   ├── shop.prisma
│   └── erp.prisma

Nest 篇

项目结构

bash
prisma
├── 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.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"; // 扩展

@Global() // Global decorator to make this module available globally
@Module({
  imports: [],
  controllers: [],
  providers: [PrismadbService, PrismaProvider, PrismaQueryHelperService],
  exports: [PrismadbService, PrismaProvider, PrismaQueryHelperService],
})
export class PrismadbModule {}

prisma.provider.ts

ts
import { Injectable, OnModuleDestroy, OnModuleInit } from "@nestjs/common";
import { PrismaClient } from "../../../prisma/generated/prisma"; // 修改为你自己的
import { PrismaQueryHelperService } from "./prisma.extension.service"; // 扩展
// 读写分离 这样读是一个数据库 写又是另外一个数据库
import { readReplicas } from "@prisma/extension-read-replicas";
import { ConfigService } from "@nestjs/config";
@Injectable()
export class PrismaProvider
  extends PrismaClient
  implements OnModuleInit, OnModuleDestroy
{
  private static initialized = false;

  constructor(
    private readonly configService: ConfigService,
    private readonly PrismaQueryHelperService: PrismaQueryHelperService
  ) {
    super({
      datasourceUrl: `mysql://${configService.get("db").mysql.username}:${
        configService.get("db").mysql.password
      }@${configService.get("db").mysql.host}:${
        configService.get("db").mysql.port
      }/${configService.get("db").mysql.database}`,
      log: ["info", "query"],
    });
  }

  async onModuleInit() {
    if (!PrismaProvider.initialized) {
      PrismaProvider.initialized = true;
      await this.$connect();
    }
  }

  async onModuleDestroy() {
    if (PrismaProvider.initialized) {
      PrismaProvider.initialized = false;
      await this.$disconnect();
    }
  }
  // 增加扩展 // 读写分离
  withExtensions() {
    // 读的客户端
    const readReplicasClient = new PrismaClient({
      datasourceUrl: `mysql://${
        this.configService.get("db").mysqlread.username
      }:${this.configService.get("db").mysqlread.password}@${
        this.configService.get("db").mysqlread.host
      }:${this.configService.get("db").mysqlread.port}/${
        this.configService.get("db").mysqlread.database
      }`,
      log: ["info", "query"],
    });
    return this.$extends(
      this.PrismaQueryHelperService.existsExtension
    ).$extends(
      readReplicas({
        replicas: [readReplicasClient],
      })
    );
  }
}

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.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);
  }
}

使用

  • 返回结果就是 true 或者 false
ts
  async testextextends() {
    //  返回布尔值
    const result = await this.prismadbService.emp.exists({ id: { gte: 100 } });
    console.log(result);
    // 返回查询结果
    return result;
  }

配置文件

bash
# 开发环境配置
app:
  prefix: ''
  port: 8080
  logger:
    # 项目日志存储路径,相对路径(相对本项目根目录)或绝对路径
    dir: '../logs'
  # 文件相关
  file:
    # 是否为本地文件服务或cos
    isLocal: true
    # location 文件上传后存储目录,相对路径(相对本项目根目录)或绝对路径
    location: '../upload'
    # 文件服务器地址,这是开发环境的配置 生产环境请自行配置成可访问域名
    domain: 'http://localhost:8080'
    # 文件虚拟路径, 必须以 / 开头, 如 http://localhost:8080/profile/****.jpg  , 如果不需要则 设置 ''
    serveRoot: '/profile'
    # 文件大小限制,单位M
    maxSize: 10
# 腾讯云cos配置
cos:
  secretId: ''
  secretKey: ''
  bucket: ''
  region: ''
  domain: ''
  location: ''
# 数据库配置
db:
  mysql:
    host: 'xxxx'
    username: 'root'
    password: 'xxx'
    database: 'manybymany'
    port: 3306
    charset: 'utf8mb4'
    logger: 'file'
    logging: true
    multipleStatements: true
    dropSchema: false
    synchronize: true
    supportBigNumbers: true
    bigNumberStrings: true
  mysqlread:
    host: 'xxxx'
    username: 'root'
    password: 'xxxx'
    database: 'manybymany'
    port: 3306
    charset: 'utf8mb4'
    logger: 'file'
    logging: true
    multipleStatements: true
    dropSchema: false
    synchronize: true
    supportBigNumbers: true
    bigNumberStrings: true
# redis 配置
redis:
  host: 'xxxx'
  password: 'redis_n6BSFa'
  port: 6379
  db: 0
  keyPrefix: ''

# jwt 配置
jwt:
  secretkey: 'jsopy'
  expiresin: '1h'
  refreshExpiresIn: '2h'
# 权限 白名单配置
perm:
  router:
    whiteList:
      ['/user/login', '/user/register', '/user/refreshToken', '/user/testjwt']
# 不使用全局拦截
noglobalinterceptor:
  router:
    whiteList: ['/user/yasuo2', '/user/yasuo']
# 用户相关
# 初始密码, 重置密码
user:
  initialPassword: '123456'

# 自己写的测试
testconfig:
  test: 'dev'