1. 课程导入与工程环境搭建

在 Node.js 与 TypeScript 生态中,访问关系型数据库长期存在两条主流技术路线:一条是以 Prisma 为代表的“Schema 优先 + 代码生成”路线,另一条是以 TypeORM 为代表的“装饰器实体 + 运行时映射”路线。两者都能完成增删改查、事务、关联查询与迁移,但它们的工程哲学、类型安全边界和团队协作方式差异明显。本章先建立整体定位认知,再完成一套可运行的环境,为后续章节的对比实验打好基础。

1.1 定位差异:Schema 优先与实体优先

Prisma 的核心是 schema.prisma 文件。开发者用 Prisma Schema Language 声明数据源、模型、字段类型与关系,随后执行 prisma generate,由 CLI 生成类型完备的 Prisma Client。数据库表结构、TypeScript 类型、查询 API 三者由同一份 Schema 派生,因此“模型改了但类型没跟上”这类问题在编译期就能暴露。它的迁移由 prisma migrate 管理,迁移文件是纯 SQL,可审阅、可版本化。

TypeORM 的核心是实体类。开发者用 @Entity()、@Column()、@ManyToOne() 等装饰器描述表结构,通过 DataSource 建立连接,用 Repository 或 QueryBuilder 发起查询。实体类既是数据库映射,也是业务代码可直接引用的类型,灵活度高,支持 Active Record 与 Data Mapper 两种风格。代价是装饰器元数据依赖 reflect-metadata 与 emitDecoratorMetadata,类型安全更多依赖开发者约束,复杂关联查询容易出现类型与运行时不一致。

Prisma 与 TypeORM 的工程链路对比 Prisma:Schema 优先 schema.prisma model / datasource / generator prisma generate Prisma Client(生成代码) 类型安全查询 API prisma migrate SQL 迁移文件 可审阅、可版本化 PostgreSQL / MySQL / SQLite TypeORM:实体优先 @Entity() 实体类 @Column / @ManyToOne 装饰器 DataSource 初始化 Repository / QueryBuilder 运行时元数据映射 migration:generate 迁移文件 由实体差异生成 PostgreSQL / MySQL / SQLite

1.2 适用场景判断

如果团队重视数据库结构的单一事实来源、希望查询结果类型与 Schema 严格一致、且愿意接受“改模型后必须重新生成客户端”的约束,Prisma 通常更省心。它特别适合中后台 API、Serverless 函数、需要频繁迭代表结构的早期项目。如果项目需要大量动态 SQL、复杂多表 JOIN、存储过程调用,或已有大量基于装饰器的实体代码,TypeORM 的 QueryBuilder 与原生 SQL 逃生舱会更顺手。它常见于传统 MVC 分层项目、需要精细控制 SQL 的场景。

需要强调的是,两者并非互斥。同一项目可以同时使用 Prisma 处理常规 CRUD、TypeORM 处理遗留模块,但会带来两套连接池、两套迁移体系的维护成本,除非有明确收益,否则不建议在同一个服务里混用。

1.3 Node.js 与 TypeScript 环境配置

建议使用 Node.js 20 LTS 或 22 LTS,TypeScript 5.x。先初始化项目并安装依赖:

mkdir prisma-typeorm-lab && cd prisma-typeorm-lab
npm init -y
npm i -D typescript ts-node @types/node
npx tsc --init

TypeORM 需要装饰器支持,tsconfig.json 中必须开启以下选项:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist"
  }
}

Prisma 不依赖装饰器,但同样建议开启 strict,以便生成客户端的类型检查发挥最大作用。随后安装两套运行时:

npm i @prisma/client typeorm reflect-metadata pg
npm i -D prisma

注意 reflect-metadata 必须在应用入口最顶部导入一次,否则 TypeORM 的列类型推断会失败:

import 'reflect-metadata';

1.4 数据库选型与连接准备

本课程实验统一使用 PostgreSQL 16,原因是它同时被 Prisma 与 TypeORM 良好支持,且具备 JSONB、数组、枚举等便于演示高级特性的类型。若本机没有 PostgreSQL,可用 Docker 快速启动:

docker run -d --name pg-lab \
  -e POSTGRES_USER=lab \
  -e POSTGRES_PASSWORD=lab123 \
  -e POSTGRES_DB=shop \
  -p 5432:5432 postgres:16

连接串为 postgresql://lab:lab123@localhost:5432/shop。Prisma 在 .env 中通过 DATABASE_URL 读取;TypeORM 则在 DataSource 配置对象中显式传入。两者都建议把连接信息放在环境变量里,不要硬编码进源码。

SQLite 适合快速验证语法,但缺少原生枚举与并发写入能力,不建议作为本课程主实验库。MySQL 8 也可用,只需把驱动换成 mysql2 并调整连接串。

1.5 初始化两套工程骨架

Prisma 侧执行:

npx prisma init --datasource-provider postgresql

这会生成 prisma/schema.prisma 与 .env。在 Schema 中先定义一个最小模型用于连通性验证:

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
}

然后执行 npx prisma migrate dev --name init,它会创建数据库表、生成迁移文件并重新生成客户端。若命令成功且 node_modules/.prisma/client 出现,说明 Prisma 链路已通。

TypeORM 侧新建 src/data-source.ts:

import 'reflect-metadata';
import { DataSource } from 'typeorm';
import { User } from './entity/User';

export const AppDataSource = new DataSource({
  type: 'postgres',
  url: process.env.DATABASE_URL,
  entities: [User],
  synchronize: false,
  logging: ['query', 'error'],
});

实体类用装饰器描述同样的表结构,随后调用 AppDataSource.initialize() 并执行一次 repository.find(),若能返回空数组且无报错,说明 TypeORM 链路也已打通。

1.6 环境自检清单

检查项Prisma 侧TypeORM 侧
Node 版本≥ 20 LTS≥ 20 LTS
装饰器配置不需要experimentalDecorators + emitDecoratorMetadata
元数据导入不需要入口顶部 import 'reflect-metadata'
连接来源.env 中 DATABASE_URLDataSource 配置或环境变量
连通验证prisma migrate dev 成功initialize() 后 find() 无异常
迁移策略prisma migratemigration:generate / run