从零打造 AI 全栈应用(六):NestJS + Prisma 打造企业级 API 后端

148 阅读4分钟

本文是「从零打造 AI 全栈应用」系列第 篇,承接上一篇的前端与工程化内容,正式进入后端 API 层的系统化建设。

本篇目标不是“教你用 Prisma 写几行 CRUD”,而是站在真实企业级项目和大厂面试官视角,完整拆解:

  • 为什么选 NestJS + Prisma
  • 一个可长期演进的后端项目,数据库与代码应该如何组织
  • ORM、DTO、Migration、依赖注入在真实项目中的协作方式

一、为什么后端是 AI 全栈中最重要的一环?

很多同学在做 AI 应用时,会有一个误区:

“AI 才是核心,后端就是调个接口存点数据。”

这是不对的。

在真实项目中,后端承担的是:

  • 业务规则的唯一可信来源
  • 数据一致性与安全性保证
  • AI 能力的编排层(Prompt / 模型调用 / 权限 / 计费)

前端可以重写,模型可以更换,但后端的数据结构和架构一旦烂掉,项目基本宣告死亡

因此这一篇,我们从一个干净的 API 项目开始。


二、项目初始化:NestJS 的工程化优势

nest new posts

选择 NestJS 的原因只有一句话:

它是目前最接近“后端工程规范标准答案”的 Node.js 框架。

NestJS 的三个核心特性

  1. 高度模块化(Module)

    • 每个业务域天然隔离
    • 非常适合中大型项目
  2. 依赖注入(DI)

    • Service 不关心实例如何创建
    • 天生适合数据库、缓存、第三方服务
  3. 强类型 + 装饰器语义化

    • 非常利于团队协作和代码可读性

大厂面试中,如果你能讲清 Nest 的 Module / Provider / DI,基本已经超过 80% 候选人。


三、ORM 的引入:为什么是 Prisma?

1️⃣ ORM 是什么?

ORM(Object Relation Mapping)= 对象关系映射

它做了一件事:

数据库ORM 世界
TableClass
RowInstance
ColumnProperty

你不再直接写 SQL,而是:

prisma.user.create()
prisma.post.findMany()

本质上是:

后端代码 → Prisma → SQL

2️⃣ 为什么不用“手写 SQL”?

  • SQL 可维护性差
  • 无类型约束
  • 表结构变化成本极高

而 Prisma:

  • Schema 即数据库设计稿
  • 强类型 + 自动补全
  • Migration 可追溯

Prisma 在团队协作、长期维护、代码安全性上,几乎是降维打击。


四、Prisma 的完整初始化流程

1️⃣ 安装依赖(版本必须对齐)

pnpm i prisma@6.19.2
pnpm i @prisma/client@6.19.2

⚠️ prisma CLI 和 @prisma/client 版本必须一致,这是常见踩坑点。

2️⃣ 创建数据库

CREATE DATABASE xue WITH OWNER=postgres ENCODING='UTF8';

这是真正的起点,数据库先于代码存在。

3️⃣ 初始化 Prisma

npx prisma init

你会得到:

  • prisma/schema.prisma → 数据库设计稿
  • .env → 数据库连接字符串

五、Schema:数据库的“宪法文件”

在后端项目中,schema 的重要性 ≈ 前端设计稿

它不是“随便写写”,而是:

  • 数据结构的最终裁决
  • 业务边界的体现
  • 迁移、生成、类型的源头

1️⃣ Model ≈ 表

model User {
  id        Int     @id @default(autoincrement())
  name      String  @unique @db.VarChar(255)
  password  String  @db.VarChar(255)
  posts     Post[]
  @@map("users")
}

关键点解析

  • @id:主键
  • @default(autoincrement()):自增
  • @unique:唯一约束
  • @@map:数据库真实表名

Prisma 的设计理念是:代码可读性优先,数据库命名通过 map 适配


六、关系建模:真实业务的核心难点

多对多:Post ↔ Tag

model PostTag {
  postId Int
  tagId  Int

  post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
  tag  Tag  @relation(fields: [tagId], references: [id], onDelete: Cascade)

  @@id([postId, tagId])
}

这是标准的中间表建模方式

  • 复合主键防止重复
  • 关系显式、可控

能正确建模多对多关系,是判断一个后端是否“入门以上”的重要标准。


七、Migration:让数据库“可进化”

npx prisma migrate dev --name init-user

Migration 的价值:

  • 记录每一次表结构变更
  • 可回滚、可追溯
  • 多人协作不再靠“口头同步”

没有 Migration 的后端项目,一定是事故现场。


八、Prisma Client 与 NestJS 的融合

1️⃣ 为什么要封装 PrismaService?

我们不应该在每个 Service 里直接 new PrismaClient。

正确做法是:

  • 全局唯一实例
  • 由 Nest 管理生命周期

2️⃣ PrismaModule(全局注入)

@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

3️⃣ PrismaService

@Injectable()
export class PrismaService
  extends PrismaClient
  implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
}

这一步,体现的是依赖注入 + 资源生命周期管理,是 Nest 的精髓。


九、DTO:让接口“有边界感”

DTO(Data Transfer Object)解决的是:

  • 前端乱传参数
  • 类型不可信
  • 校验逻辑分散

DTO 的职责链路

前端 → DTO → Controller → Service → 数据库

class-validator + class-transformer

  • 参数校验流程化
  • 多传的字段自动丢弃
  • 少传的字段直接报错

DTO 是接口契约,不是“写着好看”。


十、文章列表接口背后的真实复杂度

一个看似简单的「文章列表」接口,背后往往是:

  • posts 列表
  • total count(分页)
  • tags 聚合
  • likes 统计
  • files / avatar 关联

ORM 的价值就在这里体现:

把多条 SQL 的复杂度,压缩成一次类型安全的查询。