nestjs 学习 18:prisma 通关

Prisma 是面向 Node.js/TypeScript 的下一代 ORM(对象关系映射)工具,核心是类型安全、声明式建模、自动迁移、可视化管理,彻底告别手写 SQL 易错、传统 ORM 复杂映射的痛点。

Prisma 由 3 大核心模块构成,分工明确:

  • Prisma Schemaschema.prisma 声明式文件,统一管理数据库连接、数据模型、生成器配置。

  • Prisma Client :自动生成的类型安全查询客户端,提供完整 CRUD API,IDE 自动补全、编译期校验。

  • Prisma Migrate:数据库迁移工具,版本化管理表结构变更,支持回滚、增量同步。

  • Prisma Studio:内置 Web 可视化工具,直接查看 / 编辑数据库数据。

下面以mysql为例子介绍怎么使用 prisma。

同时,以新项目和老项目来分别介绍。

新启动的项目

对于新项目,参考官方文档(www.prisma.io/docs/prisma...%25E3%2580%2582 "https://www.prisma.io/docs/prisma-orm/quickstart/mysql)%E3%80%82")

首先需要安装:

js 复制代码
pnpm add prisma --save-dev

pnpm add @prisma/client @prisma/adapter-mariadb dotenv
  • prisma: Prisma CLI 工具集,它是命令行工具,只在开发 / 构建 / 部署时用,生产环境不需要。

  • @prisma/client: Prisma ORM 的自动生成的、类型安全的数据库客户端。它是你业务代码里唯一用来和数据库交互的工具

它会根据你的 schema.prisma 自动生成 CRUD API。

当你改动schema.prisma文件后:

js 复制代码
model User {
  id    Int
  name  String
  email String
}

然后执行 prisma generate就会自动生成 api:

js 复制代码
prisma.user.findMany()
prisma.user.findUnique()
prisma.user.create()
prisma.user.update()
prisma.user.delete()
  • @prisma/adapter-mariadb:它是 Prisma Client 与 MySQL/MariaDB 之间的官方的翻译层 + 驱动桥接。它让 Prisma 不再内置数据库驱动,而是对接 JS 生态标准驱动(mariadb),Prisma Client 7.x 必须配适配器才能连数据库(强制)。

那什么是数据库驱动呢?

你的代码(Node.js)根本不懂 MySQL 协议 ,MySQL 也听不懂 Node.js 的语言

必须有一个中间件:

  • 把你的指令 → 翻译成 MySQL 能懂的二进制协议
  • 把 MySQL 返回的数据 → 翻译成 JS 对象给你

这个中间件,就叫 驱动(Driver)

接下来看怎么使用。

首先执行:

js 复制代码
pnpm dlx prisma init --datasource-provider mysql --output ../generated/prisma

会创建三个文件:

  • prisma/schema.prisma
js 复制代码
generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "mysql"
}
  • .env
js 复制代码
// 把username和password替换为真实的
DATABASE_URL="mysql://username:password@localhost:3306/mydb"
// 自己手动添加
DATABASE_USER="username"
DATABASE_PASSWORD="password"
DATABASE_NAME="mydb"
DATABASE_HOST="localhost"
DATABASE_PORT=3306
  • prisma.config.ts:
js 复制代码
import "dotenv/config";
import { defineConfig, env } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
  },
  datasource: {
    url: env("DATABASE_URL"),
  },
});

接下来定义数据模型:

js 复制代码
generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "mysql"
}

model User { 
  id    Int     @id @default(autoincrement()) 
  email String  @unique
  name  String?
  posts Post[]
} 

model Post { 
  id        Int     @id @default(autoincrement()) 
  title     String
  content   String? @db.Text
  published Boolean @default(false) 
  author    User    @relation(fields: [authorId], references: [id]) 
  authorId  Int
} 

接着执行:

js 复制代码
pnpm dlx prisma migrate dev --name init

它依次会做如下事情:

  1. 对比新旧模型

Prisma 会对比:

  • 你现在的 schema.prisma
  • 你当前数据库的表结构

找出差异:加了什么表?加了什么字段?改了什么类型?

  1. 生成一个迁移文件(.sql)

prisma/migrations/ 里生成一个文件夹,里面有:

  • migration.sql (真正的 MySQL 建表 / 改表 SQL)
  • 记录版本、时间、名字

这个文件以后可以在生产环境直接执行

  1. 在本地 MySQL 执行这个 SQL

真正去修改你的本地库:

  • 新建表
  • 添加字段
  • 删除字段
  • 修改字段类型
  • 添加索引 / 外键

这一步让你的本地库结构和 schema 保持一致。

  1. 记录迁移历史 在 MySQL 里创建一张 _prisma_migrations 表,记录:
  • 哪些迁移已经执行
  • 什么时候执行的
  • 防止重复执行

最后执行:

js 复制代码
pnpm dlx prisma generate

你写的 schema.prisma 是模型图纸。prisma generate 做的事:拿着你的图纸 → 生成可以直接在代码里用的。

  • prisma.user.findMany()
  • prisma.user.create()
  • prisma.post.update()
  • 所有 TS 类型提示
  • 所有自动补全
  • 所有数据库方法

生成的内容全部在这里generated/prisma/client

为什么你必须手动执行它?

因为你修改了 schema.prisma,类型和方法不会自动更新,必须执行 prisma generate 重新生成。

否则,你加的新字段 VS Code 不提示 ,你加的新表 代码里找不到 prisma.xxx ,TS 报错 找不到字段 / 类型

现在客户端代码生成好了,那么就实例化一个client 去具体的执行增删改查。

创建lib/prisma.ts

js 复制代码
import "dotenv/config";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import { PrismaClient } from "../generated/prisma/client";

const adapter = new PrismaMariaDb({
  host: process.env.DATABASE_HOST,
  user: process.env.DATABASE_USER,
  password: process.env.DATABASE_PASSWORD,
  database: process.env.DATABASE_NAME,
  connectionLimit: 5,
});
const prisma = new PrismaClient({ adapter });

export { prisma };

创建一个脚本:

js 复制代码
import { prisma } from "./lib/prisma";

async function main() {
  const user = await prisma.user.create({
    data: {
      name: "Alice",
      email: "alice@prisma.io",
      posts: {
        create: {
          title: "Hello World",
          content: "This is my first post!",
          published: true,
        },
      },
    },
    include: {
      posts: true,
    },
  });
  console.log("Created user:", user);

  // Fetch all users with their posts
  const allUsers = await prisma.user.findMany({
    include: {
      posts: true,
    },
  });
  console.log("All users:", JSON.stringify(allUsers, null, 2));
}

main()
  .then(async () => {
    await prisma.$disconnect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });

执行pnpm dlx tsx script.ts就往数据库插入数据了。

总结下流程:

  • 修改schema.prisma,对表结构进行修改;
  • 执行pnpm dlx prisma migrate dev --name add_user_table,把修改同步到数据库;
  • 执行 pnpm dlx prisma generate,对修改的内容生成最新的可执行的代码api;

到这里,开发环境算是完成了,那么现在要上线,就需要把数据库表结构在生产上同步一份。

怎么同步呢?

  1. prisma/migrations 提交到 Git

这个文件夹是结构迁移的核心,里面保存了所有版本的表结构。

生产环境必须拿到这个文件夹才能同步表结构。

  1. 生产环境配置数据库连接

在生产环境的 .env 里写生产 MySQL 地址:

js 复制代码
DATABASE_URL="mysql://user:pass@localhost:3306/production_db"
  1. 生产环境执行一条命令:同步表结构
js 复制代码
pnpm dlx prisma migrate deploy

这条命令做什么?

  • 只同步表结构
  • 不会创建 / 删除 / 修改任何业务数据
  • 不会导入测试数据
  • 不会删除表
  • 不会清空数据
  • 只会执行还没在生产库执行过的 migrations 文件
  • 安全、只读结构、不碰数据

如果有些种子数据需要导入进去,怎么办呢?

使用prisma db seed即可。

  1. 创建种子数据的脚本prisma/seed.ts
js 复制代码
import { prisma } from './prismaClient'

async function main() {
  // 1. 创建默认角色
  await prisma.role.upsert({
    where: { name: 'USER' },
    update: {},
    create: {
      name: 'USER',
      desc: '普通用户'
    },
  })

  // 2. 创建管理员
  await prisma.user.upsert({
    where: { email: 'admin@example.com' },
    update: {},
    create: {
      email: 'admin@example.com',
      name: 'Admin',
      password: 'xxx', // 生产环境请加密
    },
  })

  console.log('✅ 种子数据插入完成')
}

main()
  .catch((e) => console.error(e))
  .finally(async () => await prisma.$disconnect())
  1. prisma.config.ts 注册种子命令 seed: 'tsx prisma/seed.ts'
js 复制代码
import 'dotenv/config'
import { defineConfig } from 'prisma/config'

export default defineConfig({
  schema: 'prisma/schema.prisma',
  migrations: {
    path: 'prisma/migrations',
    seed: 'tsx prisma/seed.ts',
  },
  datasource: {
    url: process.env['DATABASE_URL'],
  },
})
  1. 执行npx prisma db seed就完成了种子数据的插入

老项目

如果数据库已经存在,并且有业务数据,后端项目没有使用Prisma,现在改用 Prisma,该怎么做呢?

  1. 先初始化 Prisma
js 复制代码
pnpm dlx prisma init --datasource-provider mysql
  1. 修改 .env 连接现有数据库
js 复制代码
DATABASE_URL="mysql://用户名:密码@localhost:3306/你的已有数据库名"
  1. 从现有数据库反向生成 schema.prisma
js 复制代码
pnpm dlx prisma db pull

它会做什么?

  • 读取你数据库里所有表
  • 自动生成 model
  • 自动识别字段类型(int/varchar/datetime 等)
  • 自动识别主键、外键、唯一键
  • 自动生成完整的 schema.prisma
  • 完全不碰你的数据,不删不改任何表
  1. 生成客户端代码
js 复制代码
pnpm dlx prisma generate

现在你可以直接用 Prisma 操作老数据库了:

js 复制代码
prisma.你的表名.findMany() 
prisma.你的表名.create()
  1. 我修改了 schema.prisma,怎么办?

这个时候就不能执行pnpm dlx prisma generate了,执行会报:表已经存在 → 试图创建 → 报 Table already exists 错误,它只适用于全新的项目。

需要执行:

js 复制代码
pnpm dlx prisma db push 
pnpm dlx prisma generate

如果我就是想用migrate dev(带版本化迁移)呢?

这是 Prisma 官方高级流程,叫 基线迁移(Baseline) 。官方文档:www.prisma.io/docs/orm/pr...

必须满足两个条件:

  • 在开始使用 Prisma Migrate 之前就已存在的数据库
  • 包含必须保留的数据(如生产环境),即数据库不能被重置

基线化就是通过告诉 Prisma Migrate 假装初始迁移已经被应用,也就是当前所有表已经存在,不要重新创建,从现在开始记录变更,这可以防止生成的迁移在尝试创建已存在的表和字段时失败。

如何创建基线迁移?

第一步: 如果已有 prisma/migrations 文件夹,请删除该文件夹,然后创建新的目录:

js 复制代码
mkdir -p prisma/migrations/0_init

第二步: 使用 prisma migrate diff 生成迁移文件(注意目录前缀使用 0_,以确保 Prisma Migrate 按字典顺序应用迁移):

js 复制代码
npx prisma migrate diff \
  --from-empty \
  --to-schema prisma/schema.prisma \
  --script > prisma/migrations/0_init/migration.sql

第三步: 审查生成的迁移文件,然后运行以下命令将其标记为已应用:

js 复制代码
npx prisma migrate resolve --applied 0_init

此命令会将目标迁移添加到 _prisma_migrations 表并标记为已应用。之后当您运行 prisma migrate deploy 时,Prisma Migrate 将:

  1. 跳过所有标记为"已应用"的迁移(包括基线迁移)
  2. 应用基线迁移之后的所有新迁移

现在你可以正常使用:

js 复制代码
pnpm dlx prisma migrate dev --name xxx

prisma 应用在 nestjs中

首先还是安装必要的库,接着进行初始化:

js 复制代码
npx prisma init

改下 .env 的配置,然后创建 model。

然后创建新的 migration:

js 复制代码
npx prisma migrate dev --name init

这时候数据库就就有这两个表了。

接着执行:

js 复制代码
npx prisma generate

生成 client 代码,接下来我们就可以直接来做 CRUD 了。

现在,创建个 Service:

js 复制代码
nest g service prisma --flat --no-spec

import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaMariaDb } from '@prisma/adapter-mariadb';
import { PrismaClient } from '../generated/prisma/client';
import 'dotenv/config';

@Injectable()
export class PrismaService
  extends PrismaClient
  implements OnModuleInit, OnModuleDestroy
{
  constructor() {
    // 1. 创建 Prisma 7.x 必须的 adapter
    const adapter = new PrismaMariaDb({
      host: process.env.DATABASE_HOST as string,
      user: process.env.DATABASE_USER as string,
      password: process.env.DATABASE_PASSWORD as string,
      database: process.env.DATABASE_NAME as string,
      connectionLimit: 5,
    });
    super({
      adapter,
      log: [
        {
          emit: 'stdout',
          level: 'query',
        },
      ],
    });
  }

  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

然后再创建一个 service,这个 service 里注入 PrismaService,不就可以 CRUD 了么?

js 复制代码
nest g service department --flat --no-spec

import { Inject, Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { Prisma } from '../generated/prisma/client';

@Injectable()
export class EmployeeService {
  @Inject(PrismaService)
  private prisma!: PrismaService;

  async create(data: Prisma.EmployeeCreateInput) {
    return await this.prisma.employee.create({
      data,
      select: {
        id: true,
      },
    });
  }
}

插入数据之后,再把 id 查询出来返回。

这里的 data 的 ts 类型不用自己定义,生成的 client 代码里有。

输入 Prisma.Deparment 就会提示出来。

然后在 AppController 里注入这个 service 就可以使用了。

prisma studio

最后介绍下prisma studio这个好用的可视化工具,执行后就可以直接增删改查。

相关推荐
织_网3 小时前
Nest.js:Node.js后端开发的现代企业级解决方案,赋能AI全栈开发
javascript·人工智能·node.js
Leisureconfused3 小时前
【记录】Node版本兼容性问题及解决
前端·vue.js·npm·node.js
小p19 小时前
nestjs 学习17:封装一个微服务注册与配置中心的动态模块
node.js
老王以为19 小时前
深入理解 AbortController:从底层原理到跨语言设计哲学
javascript·设计模式·node.js
渠过客20 小时前
【运维】PM2 使用完全指南:Node.js 应用进程管理利器
运维·node.js
小粉粉hhh1 天前
Node.js(一)——初始Node.js
node.js
不会写程序的未来程序员1 天前
nvm 安装教程:Node.js 版本管理全攻略 (Win/Mac/Linux) + .nvmrc 实战
linux·macos·node.js·前端开发·环境配置·nvm
米丘1 天前
Vite 开发服务器启动时,如何将 client 注入 HTML?
javascript·node.js·vite
米丘1 天前
vite 插件 @vitejs/plugin-vue
javascript·node.js·vite