Skip to content
当前页大纲

先前我们已经创建好了一个基础的NestJS工程,接下来我们来给它加上数据库功能

本例子将使用Prisma连接数据库

创建数据库服务

我这里使用的是一个免费的线上服务进行临时测试,具体可以根据自己手头上的资源进行选择

创建完毕后将相关配置信息填入环境变量文件

集成插件

sh
nest add nestjs-prisma
pnpm add -D dotenv-cli

数据库配置

修改apps/api/src/app.module.ts,在imports中增加PrismaModule配置项和相关的环境变量

ts
import { PrismaModule } from 'nestjs-prisma' 
@Module({
  imports: [
    ConfigModule.forRoot({
      validationSchema: Joi.object({
        // ...
        MYSQL_URL: Joi.string().required(),
        MYSQL_HOST: Joi.string().required(),
        MYSQL_PORT: Joi.number().default(3306),
        MYSQL_USER: Joi.string().default('root'),
        MYSQL_PWD: Joi.string().required(),
        MYSQL_DBNAME: Joi.string().required(),
        CHARSET: Joi.string().default('utf8'),
        TIMEZONE: Joi.string().default('Asia/Shanghai'),
      }),
    }),
    PrismaModule.forRootAsync({
      isGlobal: true,
      inject: [ConfigService],
      useFactory: (config: ConfigService) => {
        const NODE_ENV = config.get('NODE_ENV')
        return {
          prismaOptions: {
            log: NODE_ENV === 'production' ? ['error'] : ['info', 'warn', 'error'],
            datasourceUrl: config.get('MYSQL_URL'),
          },
        }
      },
    }),
  ]
})
ts
import { PrismaModule } from 'nestjs-prisma' 
@Module({
  imports: [
    ConfigModule.forRoot({
      validationSchema: Joi.object({
        // ...
        MONGODB_URL: Joi.string().required(),
      }),
    }),
    PrismaModule.forRootAsync({
      isGlobal: true,
      inject: [ConfigService],
      useFactory: (config: ConfigService) => {
        const NODE_ENV = config.get('NODE_ENV')
        return {
          prismaOptions: {
            log: NODE_ENV === 'production' ? ['error'] : ['info', 'warn', 'error'],
            datasourceUrl: config.get('MONGODB_URL'),
          },
        }
      },
    }),
  ]
})

手动配置

sh
pnpm add @prisma/client
pnpm add -D prisma dotenv-cli

初始化Prisma

初始化Prisma,执行npx prisma init,然后项目根目录会自动生成prisma/schema.prisma文件,根据项目情况对其进行修改

ini
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider     = "mysql"
  url          = env("MYSQL_URL")
  relationMode = "prisma"
}
ini
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider     = "mongodb"
  url          = env("MONGODB_URL")
  relationMode = "prisma"
}

提示

MYSQL_URL.env文件中的数据库环境变量

relationMode的作用是定义表的外键关系模式,这里用的是prisma模式,即虚拟外键

修改tsconfig.build.json,把prisma文件夹加入排除项

json
{
  "extends": "./tsconfig.json",
  "exclude": ["node_modules", "test", "dist", "**/*spec.ts", "prisma"] 
}

Prisma模块

sh
nest g library prisma --no-spec
ts
import { Global, Module } from '@nestjs/common'
import { PrismaService } from './prisma.service'

@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}
ts
import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  constructor() {
    super({
      log: process.env.NODE_ENV === 'production' ? ['error'] : ['info', 'warn', 'error'],
    })
  }
  async onModuleInit(): Promise<void> {
    await this.$connect()
  }
}

修改apps/api/src/main.ts

ts
// ...
async function bootstrap() {
  // ...
  app.enableShutdownHooks()
}

更新启动脚本

目前的PrismaNestJS中使用时有个大问题,就是项目启动后,会因为Prisma读取了.env文件而覆盖掉NestJS中配置好的环境变量读取规则

因此启动脚本需要使用dotenv-cli插件来显式指定要加载的env文件,这里以start:dev脚本为例

json
{
  "scripts": {
    // ...
    "start:dev": "dotenv -e .env.local -- nest start --watch"
  }
}

定义模型

如果数据表之前已经建好,那么执行npx prisma db pull会自动根据表生成模型并增量更新prisma/schema.prisma文件

注意

由于Prisma默认只读取.env文件中的环境变量,如果有多份环境变量配置的话,需要手动指定env文件,例如npx dotenv -e .env.local -- npx prisma db pull

如果没有数据表,那么需要手动定义模型,这里以user表为例

编辑prisma/schema.prisma文件,增加如下内容,然后执行npx prisma db push会根据模型信息自动创建表

ini
model user {
  id       Int     @id @default(autoincrement()) @db.UnsignedInt
  username String  @db.VarChar(50)
  password String  @db.VarChar(100)
  role     Int     @default(0) @db.UnsignedTinyInt
  avatar   String? @default("") @db.VarChar(255)
  salt     String  @db.VarChar(50)
  status   Int     @default(0) @db.UnsignedTinyInt
}
ini
model user {
  id       String @id @default(auto()) @map("_id") @db.ObjectId
  avatar   String
  password String
  role     Int
  salt     String
  status   Int
  username String @unique(map: "username_1")
}

生成模型实体

sh
npx prisma generate

CURD

修改apps/api/src/auth/auth.service.ts,把之前的模拟数据替换为从数据库查出来的

ts
// 手动配置时
import { PrismaService } from '@libs/prisma' 
// 使用集成插件时
import { PrismaService } from 'nestjs-prisma' 
enum UserStatus {
  NORMAL = 0,
  LOCKED = 1,
  BANNED = 2,
}
export class AuthService {
  constructor(
    // ...
    private readonly prisma: PrismaService 
  ) {}
  async validateUser(data: { username: string; password: string }): Promise<ValidResult> {
    const user = await this.prisma.user.findFirst({ where: { username: data.username } }) 
    // ...
  }
}

MIT License.