Prisma在NestJS中的完整集成
Prisma在NestJS中的完整集成
学习目标:掌握 Prisma 在 NestJS 中的完整集成流程,理解 DI 系统与 Prisma 的结合使用。
一、安装 Prisma 依赖
1.1 安装 Prisma CLI
# 安装 Prisma 为开发依赖
pnpm add -D prisma
# 或使用 npm
npm install --save-dev prisma
# 或使用 yarn
yarn add --dev prisma说明:
prisma是开发依赖,仅在开发时使用- 安装后可使用
npx prisma命令
1.2 查看 Prisma CLI 命令
# 查看所有可用命令
npx prisma --help常用命令列表:
| 命令 | 作用 | 使用频率 |
|---|---|---|
prisma init | 初始化 Prisma | |
prisma generate | 生成 Prisma Client | |
prisma migrate dev | 创建迁移并应用(开发环境) | |
prisma migrate deploy | 应用迁移(生产环境) | |
prisma db push | 推送 schema 到数据库(不创建迁移) | |
prisma db pull | 拉取数据库结构到 schema | |
prisma studio | 打开可视化管理界面 | |
prisma validate | 校验 schema 文件 | |
prisma format | 格式化 schema 文件 |
1.3 安装 VSCode 插件
插件名称:Prisma
作用:
- 语法高亮
- 自动格式化
- 智能提示
- 错误检查
安装步骤:
- 打开 VSCode 扩展面板(
Cmd+Shift+X) - 搜索
Prisma - 安装官方插件(第一个)
二、初始化 Prisma
2.1 初始化命令
# 初始化 Prisma 并指定数据库类型
npx prisma init --datasource-provider postgresql
# 其他数据库示例
npx prisma init --datasource-provider mysql
npx prisma init --datasource-provider sqlite
npx prisma init --datasource-provider mongodb支持的数据源:
postgresql推荐mysqlsqlitesqlservermongodbcockroachdb
2.2 生成的文件结构
项目根目录/
├── prisma/
│ └── schema.prisma # 数据模型定义文件
├── .env # 环境变量配置
└── node_modules/
└── @prisma/
└── client/ # Prisma Client(执行 generate 后生成)2.3 .env 文件配置
生成的 .env 文件:
# PostgreSQL 连接字符串
DATABASE_URL="postgresql://pguser:example@localhost:5432/pgdb?schema=public"连接字符串格式解析:
postgresql://用户名:密码@服务器地址:端口/数据库名?schema=public
│ │ │ │ │ │
│ │ │ │ │ └── 数据库名称
│ │ │ │ └────── 端口号
│ │ │ └─────────────── 服务器地址(IP 或域名)
│ │ └──────────────────── 密码
│ └─────────────────────────── 用户名
└───────────────────────────────────── 协议不同环境配置:
# 开发环境
DATABASE_URL="postgresql://pguser:example@localhost:5432/pgdb?schema=public"
# 生产环境
DATABASE_URL="postgresql://produser:prodpass@production-server:5432/proddb?schema=public"
# 服务器环境
DATABASE_URL="postgresql://pguser:example@your-server-ip:5432/pgdb?schema=public"2.4 手动创建文件(网络问题)
如果 prisma init 失败,可手动创建:
步骤 1:创建文件夹和文件
# 创建 prisma 文件夹
mkdir prisma
# 创建 schema.prisma 文件
touch prisma/schema.prisma
# 创建 .env 文件
touch .env步骤 2:编写 prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}步骤 3:编写 .env
DATABASE_URL="postgresql://pguser:example@localhost:5432/pgdb?schema=public"三、定义数据模型(Schema)
3.1 Schema 文件结构
prisma/schema.prisma:
// 数据源配置
datasource db {
provider = "postgresql" // 数据库类型
url = env("DATABASE_URL") // 连接字符串(从 .env 读取)
}
// 客户端生成器配置
generator client {
provider = "prisma-client-js" // 生成 JavaScript/TypeScript 客户端
}
// 数据模型定义
model HomeResources {
id Int @id @default(autoincrement())
title String?
subtitle String?
url String?
image String?
desc String?
module String @default("home")
type String?
icon String?
@@map("home_resources") // 映射到数据库表名
}
model User {
id Int @id @default(autoincrement())
nickname String
type Int @default(0)
expire DateTime?
status Int @default(0)
phone String?
email String?
unionId String? @map("union_id")
openId String? @map("open_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("users") // 映射到数据库表名
}3.2 字段类型与属性
常用字段类型:
| Prisma 类型 | 对应数据库类型 | 说明 |
|---|---|---|
Int | INTEGER | 整数 |
String | VARCHAR/TEXT | 字符串 |
Boolean | BOOLEAN | 布尔值 |
DateTime | TIMESTAMP | 日期时间 |
Float | DOUBLE | 浮点数 |
Decimal | DECIMAL | 高精度小数 |
BigInt | BIGINT | 大整数 |
Bytes | BYTEA | 二进制数据 |
Json | JSON | JSON 数据 |
字段属性详解:
model User {
// 主键 + 自增
id Int @id @default(autoincrement())
// 可选字段(允许 null)
name String?
// 唯一约束
email String @unique
// 默认值
status Int @default(0)
// 默认当前时间
createdAt DateTime @default(now())
// 自动更新时间
updatedAt DateTime @updatedAt
// 映射数据库字段名
unionId String? @map("union_id")
// 注释
type Int @default(0) // 0-普通用户,1-会员,2-高级会员
}可选字段(?)说明:
model User {
// 必填字段(不允许 null)
name String
// 可选字段(允许 null)
nickname String?
phone String?
email String?
}@map 和 @@map 的区别:
model User {
id Int @id @map("_id") // 字段映射:id → _id
@@map("users") // 表名映射:User → users
}3.3 完整示例:首页资源表
model HomeResources {
id Int @id @default(autoincrement())
title String? @db.VarChar(100) // 标题
subtitle String? @db.VarChar(200) // 副标题
url String? @db.VarChar(500) // 链接地址
image String? @db.VarChar(500) // 图片地址
desc String? @db.Text // 描述
module String @default("home") @db.VarChar(50) // 模块:home/study
type String? @db.VarChar(50) // 类型:banner/image/project
icon String? @db.VarChar(100) // 图标
order Int @default(0) // 排序
status Int @default(0) // 状态:0-正常,1-禁用
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("home_resources")
}四、Prisma 工作流
4.1 完整工作流程图
Prisma 工作流程:
│
├── 第一步:定义 Schema
│ └── 编写 prisma/schema.prisma
│
├── 第二步:生成 Prisma Client
│ ├── 执行 `npx prisma generate`
│ ├── 生成类型定义文件
│ └── 安装 @prisma/client
│
├── 第三步:创建数据库迁移
│ ├── 执行 `npx prisma migrate dev --name init`
│ ├── 生成迁移文件(migrations/*.sql)
│ └── 应用到数据库
│
└── 第四步:使用 Prisma Client
├── 导入 PrismaClient
└── 执行 CRUD 操作4.2 第一步:生成 Prisma Client
# 生成 Prisma Client
npx prisma generate作用:
- 根据 schema.prisma 生成 TypeScript 类型定义
- 自动安装
@prisma/client依赖 - 生成 Prisma Client API
生成的文件:
node_modules/.prisma/client/
├── index.js
├── index.d.ts
├── schema.prisma
└── runtime/在代码中使用:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// 查询所有 HomeResources
const resources = await prisma.homeResources.findMany();
// 类型自动推断
// resources: HomeResources[]4.3 第二步:创建数据库迁移
# 创建迁移并应用到数据库
npx prisma migrate dev --name init执行过程:
1. 连接数据库
2. 比对 schema 与数据库差异
3. 生成迁移 SQL 文件
4. 应用到数据库
5. 生成 Prisma Client生成的迁移文件:
prisma/migrations/
└── 20231001000000_init/
└── migration.sqlmigration.sql 示例:
-- CreateTable
CREATE TABLE "home_resources" (
"id" SERIAL NOT NULL,
"title" VARCHAR(100),
"subtitle" VARCHAR(200),
"url" VARCHAR(500),
"image" VARCHAR(500),
"desc" TEXT,
"module" VARCHAR(50) NOT NULL DEFAULT 'home',
"type" VARCHAR(50),
"icon" VARCHAR(100),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "home_resources_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "users" (
"id" SERIAL NOT NULL,
"nickname" VARCHAR(100) NOT NULL,
"type" INTEGER NOT NULL DEFAULT 0,
"expire" TIMESTAMP(3),
"status" INTEGER NOT NULL DEFAULT 0,
"phone" VARCHAR(20),
"email" VARCHAR(100),
"union_id" VARCHAR(100),
"open_id" VARCHAR(100),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);4.4 迁移命令对比
| 命令 | 环境 | 作用 | 使用场景 |
|---|---|---|---|
prisma migrate dev | 开发环境 | 创建迁移 + 应用 + 生成 Client | 开发时使用 |
prisma migrate deploy | 生产环境 | 只应用迁移 | 生产部署时使用 |
prisma migrate reset | 开发环境 | 重置数据库 + 应用所有迁移 | 开发调试时使用 |
prisma db push | 开发环境 | 直接推送 schema(不创建迁移) | 快速原型开发 |
4.5 添加到 package.json
package.json:
{
"scripts": {
"prisma:generate": "prisma generate",
"prisma:migrate:dev": "prisma migrate dev",
"prisma:migrate:deploy": "prisma migrate deploy",
"prisma:studio": "prisma studio",
"prisma:reset": "prisma migrate reset",
"preinstall": "prisma generate"
}
}说明:
preinstall:安装依赖前自动生成 Prisma Client- 确保每次
npm install后类型定义是最新的
五、在 NestJS 中集成 Prisma
5.1 基础使用方式(不推荐)
直接在 Service 中创建实例:
// 不推荐:每次请求都创建新实例
import { PrismaClient } from '@prisma/client';
@Injectable()
export class AppService {
getHomeResources() {
const prisma = new PrismaClient(); // 每次都创建新实例
return prisma.homeResources.findMany();
}
}问题:
- 性能低下
- 占用大量数据库连接池
- 没有利用 NestJS 的 DI 系统
5.2 使用 NestJS CLI 创建模块
# 创建 Prisma 模块
nest g module prisma
# 创建 Prisma 服务(不创建测试文件)
nest g service prisma --no-spec生成的文件结构:
src/
├── prisma/
│ ├── prisma.module.ts
│ └── prisma.service.ts
└── app.module.ts5.3 创建 Prisma Service
src/prisma/prisma.service.ts:
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
constructor() {
super({
log: [
{ emit: 'event', level: 'query' },
{ emit: 'stdout', level: 'info' },
{ emit: 'stdout', level: 'warn' },
{ emit: 'stdout', level: 'error' },
],
});
}
// 应用启动时连接数据库
async onModuleInit() {
await this.$connect();
console.log(' Prisma connected to database');
}
// 应用关闭时断开连接
async onModuleDestroy() {
await this.$disconnect();
console.log(' Prisma disconnected from database');
}
}说明:
extends PrismaClient:继承 PrismaClient 的所有方法OnModuleInit:应用启动时自动连接数据库OnModuleDestroy:应用关闭时自动断开连接log:配置日志输出
5.4 创建 Prisma Module
src/prisma/prisma.module.ts:
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global() // 标记为全局模块
@Module({
providers: [PrismaService], // 注册 PrismaService
exports: [PrismaService], // 导出 PrismaService,供其他模块使用
})
export class PrismaModule {}关键概念:
| 装饰器/属性 | 作用 | 说明 |
|---|---|---|
@Global() | 全局模块 | 无需在每个模块中重复导入 |
providers | 注册提供者 | DI 系统会初始化为单例实例 |
exports | 导出提供者 | 允许其他模块使用 |
为什么需要 exports?
NestJS 模块系统:
│
├── providers(提供者)
│ └── DI 系统会初始化实例
│
└── exports(导出)
└── 允许其他模块使用实例
如果不导出:
├── 其他模块无法使用
└── 会提示依赖注入失败5.5 在 AppModule 中导入
src/app.module.ts:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PrismaModule } from './prisma/prisma.module';
@Module({
imports: [PrismaModule], // 导入 Prisma 模块
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}5.6 在 Service 中使用
src/app.service.ts:
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma/prisma.service';
@Injectable()
export class AppService {
// 通过构造函数注入 PrismaService
constructor(private prisma: PrismaService) {}
// 查询所有首页资源
async getHomeResources() {
return this.prisma.homeResources.findMany();
}
// 查询单个资源
async getHomeResource(id: number) {
return this.prisma.homeResources.findUnique({
where: { id },
});
}
// 创建资源
async createHomeResource(data: any) {
return this.prisma.homeResources.create({
data,
});
}
// 更新资源
async updateHomeResource(id: number, data: any) {
return this.prisma.homeResources.update({
where: { id },
data,
});
}
// 删除资源
async deleteHomeResource(id: number) {
return this.prisma.homeResources.delete({
where: { id },
});
}
}对比两种方式:
// 错误方式:手动创建实例
const prisma = new PrismaClient();
const resources = await prisma.homeResources.findMany();
// 正确方式:使用 DI 系统
constructor(private prisma: PrismaService) {}
const resources = await this.prisma.homeResources.findMany();5.7 在 Controller 中使用
src/app.controller.ts:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
async getHomeResources() {
return this.appService.getHomeResources();
}
}六、NestJS DI 系统详解
6.1 DI 系统工作流程
NestJS 依赖注入系统:
│
├── 第一步:应用启动
│ └── 扫描 AppModule 及其导入的模块
│
├── 第二步:发现提供者
│ ├── 扫描 providers 数组
│ └── 发现 PrismaService
│
├── 第三步:初始化实例
│ ├── 创建 PrismaService 单例实例
│ ├── 调用 onModuleInit 生命周期钩子
│ └── 连接数据库
│
├── 第四步:注入依赖
│ ├── 扫描其他 Service 的构造函数
│ ├── 发现依赖 PrismaService
│ └── 注入单例实例
│
└── 第五步:使用实例
└── this.prisma.homeResources.findMany()6.2 providers 和 exports 的关系
PrismaModule:
│
├── providers: [PrismaService]
│ ├── DI 系统创建实例
│ └── 单例模式,全局唯一
│
└── exports: [PrismaService]
├── 允许其他模块使用
└── 如果不导出,其他模块无法访问示例说明:
// 正确:导出后可以在其他模块使用
@Global()
@Module({
providers: [PrismaService], // 创建实例
exports: [PrismaService], // 导出实例
})
export class PrismaModule {}
// 错误:没有导出,其他模块无法使用
@Module({
providers: [PrismaService], // 创建实例
// exports: [PrismaService], // 缺少导出
})
export class PrismaModule {}6.3 @Global 装饰器的作用
不使用 @Global():
// 需要在每个模块中导入 PrismaModule
@Module({
imports: [PrismaModule], // 每个模块都要导入
providers: [UserService],
})
export class UserModule {}
@Module({
imports: [PrismaModule], // 每个模块都要导入
providers: [PostService],
})
export class PostModule {}使用 @Global():
// 只需在 AppModule 中导入一次
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
// 其他模块无需导入
@Module({
providers: [UserService], // 无需导入 PrismaModule
})
export class UserModule {}七、完整集成示例
7.1 项目结构
project/
├── prisma/
│ ├── schema.prisma # 数据模型定义
│ └── migrations/ # 迁移文件
│ └── 20231001000000_init/
│ └── migration.sql
├── src/
│ ├── prisma/
│ │ ├── prisma.module.ts # Prisma 模块
│ │ └── prisma.service.ts # Prisma 服务
│ ├── app.module.ts # 根模块
│ ├── app.controller.ts # 控制器
│ └── app.service.ts # 服务
├── .env # 环境变量
├── package.json
└── tsconfig.json7.2 完整代码清单
1. prisma/schema.prisma:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model HomeResources {
id Int @id @default(autoincrement())
title String?
subtitle String?
url String?
image String?
desc String?
module String @default("home")
type String?
icon String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("home_resources")
}
model User {
id Int @id @default(autoincrement())
nickname String
type Int @default(0)
expire DateTime?
status Int @default(0)
phone String?
email String?
unionId String? @map("union_id")
openId String? @map("open_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("users")
}2. .env:
DATABASE_URL="postgresql://pguser:example@localhost:5432/pgdb?schema=public"3. src/prisma/prisma.service.ts:
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}4. src/prisma/prisma.module.ts:
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}5. src/app.module.ts:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PrismaModule } from './prisma/prisma.module';
@Module({
imports: [PrismaModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}6. src/app.service.ts:
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma/prisma.service';
@Injectable()
export class AppService {
constructor(private prisma: PrismaService) {}
async getHomeResources() {
return this.prisma.homeResources.findMany();
}
}7. src/app.controller.ts:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
async getHomeResources() {
return this.appService.getHomeResources();
}
}7.3 运行和测试
# 1. 生成 Prisma Client
npx prisma generate
# 2. 创建数据库迁移
npx prisma migrate dev --name init
# 3. 启动开发服务器
pnpm start:dev
# 4. 测试接口
curl http://localhost:3000八、最佳实践
8.1 环境变量管理
多环境配置:
// src/config/database.config.ts
import { registerAs } from '@nestjs/config';
export default registerAs('database', () => ({
url: process.env.DATABASE_URL,
}));.env.development:
DATABASE_URL="postgresql://pguser:example@localhost:5432/pgdb_dev?schema=public".env.production:
DATABASE_URL="postgresql://produser:prodpass@production-server:5432/pgdb_prod?schema=public"8.2 错误处理
全局异常过滤器:
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpStatus,
} from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { Response } from 'express';
@Catch(Prisma.PrismaClientKnownRequestError)
export class PrismaExceptionFilter implements ExceptionFilter {
catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = 'Database error';
switch (exception.code) {
case 'P2002':
status = HttpStatus.CONFLICT;
message = 'Record already exists';
break;
case 'P2025':
status = HttpStatus.NOT_FOUND;
message = 'Record not found';
break;
}
response.status(status).json({
statusCode: status,
message,
});
}
}8.3 日志配置
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
constructor() {
super({
log: [
{ emit: 'event', level: 'query' },
{ emit: 'stdout', level: 'info' },
{ emit: 'stdout', level: 'warn' },
{ emit: 'stdout', level: 'error' },
],
});
// 记录查询日志
this.$on('query', (e) => {
console.log('Query: ' + e.query);
console.log('Params: ' + e.params);
console.log('Duration: ' + e.duration + 'ms');
});
}
}九、常见问题与解决方案
9.1 网络问题导致 prisma init 失败
问题:执行 npx prisma init 失败
解决方案:
# 方案 1:重试
npx prisma init --datasource-provider postgresql
# 方案 2:手动创建文件
mkdir prisma
touch prisma/schema.prisma
touch .env9.2 Prisma Client 类型不存在
问题:Property 'homeResources' does not exist on type 'PrismaClient'
解决方案:
# 重新生成 Prisma Client
npx prisma generate9.3 数据库连接失败
问题:Can't reach database server
解决方案:
# 1. 检查 .env 文件中的连接字符串
cat .env
# 2. 检查数据库是否运行
docker compose ps
# 3. 测试连接
npx prisma db pull十、学习要点总结
10.1 核心概念速记
Prisma 集成 NestJS 核心概念:
│
├── 安装
│ ├── pnpm add -D prisma
│ └── npx prisma init --datasource-provider postgresql
│
├── Schema 定义
│ ├── model 定义表
│ ├── 字段类型(Int、String、DateTime)
│ ├── 字段属性(@id、@default、@map、?)
│ └── 表名映射(@@map)
│
├── 工作流
│ ├── prisma generate → 生成 Client
│ ├── prisma migrate dev → 创建迁移
│ └── prisma migrate deploy → 应用迁移
│
├── NestJS 集成
│ ├── 创建 PrismaService(extends PrismaClient)
│ ├── 创建 PrismaModule(@Global())
│ ├── providers: [PrismaService] → 创建实例
│ └── exports: [PrismaService] → 导出实例
│
└── DI 系统
├── providers → 初始化单例
├── exports → 允许跨模块使用
└── constructor(private prisma: PrismaService) → 注入依赖10.2 重点知识清单
| 知识点 | 重要程度 | 掌握程度 |
|---|---|---|
| Prisma 安装与初始化 | 未掌握 / 已掌握 | |
| Schema 定义语法 | 未掌握 / 已掌握 | |
| prisma generate | 未掌握 / 已掌握 | |
| prisma migrate dev | 未掌握 / 已掌握 | |
| PrismaService 创建 | 未掌握 / 已掌握 | |
| PrismaModule 配置 | 未掌握 / 已掌握 | |
| DI 系统原理 | 未掌握 / 已掌握 | |
| providers 和 exports | 未掌握 / 已掌握 |
10.3 课后思考题
- Prisma 在 NestJS 中集成的完整流程是什么?
- 为什么需要创建 PrismaService 和 PrismaModule?
- providers 和 exports 的作用分别是什么?
- 为什么需要在字段后面加
?? - @Global() 装饰器的作用是什么?