本文是「从零打造 AI 全栈应用」系列第 六 篇,承接上一篇的前端与工程化内容,正式进入后端 API 层的系统化建设。
本篇目标不是“教你用 Prisma 写几行 CRUD”,而是站在真实企业级项目和大厂面试官视角,完整拆解:
- 为什么选 NestJS + Prisma
- 一个可长期演进的后端项目,数据库与代码应该如何组织
- ORM、DTO、Migration、依赖注入在真实项目中的协作方式
一、为什么后端是 AI 全栈中最重要的一环?
很多同学在做 AI 应用时,会有一个误区:
“AI 才是核心,后端就是调个接口存点数据。”
这是不对的。
在真实项目中,后端承担的是:
- 业务规则的唯一可信来源
- 数据一致性与安全性保证
- AI 能力的编排层(Prompt / 模型调用 / 权限 / 计费)
前端可以重写,模型可以更换,但后端的数据结构和架构一旦烂掉,项目基本宣告死亡。
因此这一篇,我们从一个干净的 API 项目开始。
二、项目初始化:NestJS 的工程化优势
nest new posts
选择 NestJS 的原因只有一句话:
它是目前最接近“后端工程规范标准答案”的 Node.js 框架。
NestJS 的三个核心特性
-
高度模块化(Module)
- 每个业务域天然隔离
- 非常适合中大型项目
-
依赖注入(DI)
- Service 不关心实例如何创建
- 天生适合数据库、缓存、第三方服务
-
强类型 + 装饰器语义化
- 非常利于团队协作和代码可读性
大厂面试中,如果你能讲清 Nest 的 Module / Provider / DI,基本已经超过 80% 候选人。
三、ORM 的引入:为什么是 Prisma?
1️⃣ ORM 是什么?
ORM(Object Relation Mapping)= 对象关系映射。
它做了一件事:
| 数据库 | ORM 世界 |
|---|---|
| Table | Class |
| Row | Instance |
| Column | Property |
你不再直接写 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
⚠️
prismaCLI 和@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 的复杂度,压缩成一次类型安全的查询。