数据库集成
好的,我们来攻克任何后端应用都无法回避的核心主题:数据库集成 (Database Integration)。一个应用如果不能持久化地存储和检索数据,那它就只是一个“金鱼”,记忆只有七秒。NestJS 作为一门全能型框架,提供了与各种数据库和 ORM(对象关系映射)工具集成的优雅方案。
为你的应用装上“记忆硬盘”:NestJS 数据库集成完全指南
想象你的 NestJS 应用是一家繁忙的图书馆。
- 访客(HTTP 请求) 来到前台,想要借阅或归还书籍。
- 图书管理员(你的 Service) 负责处理这些请求。
- 巨大的书库(数据库) 存放着所有的书籍(数据)。
现在的问题是,图书管理员如何与这个巨大的书库进行高效、有序的交互?
- 直接用“梯子”一本一本找(原生驱动):图书管理员可以推着一个梯子,在浩如烟海的书架中,根据书的编码(SQL 查询语句)一本一本地寻找。这种方式最底层、最灵活,但对于复杂的“借阅清单”(复杂的业务逻辑),操作起来非常繁琐和容易出错。
- 使用“智能图书管理系统”(ORM/ODM):图书馆引入了一套智能系统。管理员只需要在电脑上输入书名(
book.name = '...
),系统就会自动告诉他这本书在哪个书架的第几层(自动生成 SQL 查询)。这套系统就是 ORM (对象关系映射) 或 ODM (对象文档映射)。
ORM/ODM 的核心思想是:让我们用操作对象 (Objects) 的方式,来间接地操作数据库中的数据。这使得开发者可以更多地关注业务逻辑,而不是费力地编写和拼接 SQL 语句。
NestJS 并不绑定任何特定的数据库技术,但它与流行的 ORM/ODM 库(如 TypeORM, Sequelize, Mongoose 等)都提供了官方的集成模块,让整合过程如丝般顺滑。
本文将以 TypeORM (用于 SQL 数据库,如 PostgreSQL, MySQL) 为例,因为它与 NestJS 的 TypeScript 特性结合得最好。
1. 第一步:搭建“智能图书管理系统” (@nestjs/typeorm
)
第一步:安装必要的依赖
# 安装 TypeORM 集成模块
npm install @nestjs/typeorm typeorm
# 安装你选择的数据库驱动
# 以 PostgreSQL 为例
npm install pg
第二步:在 AppModule
中配置数据库连接
这是最关键的一步。我们需要在根模块中使用 TypeOrmModule.forRoot()
来告诉 NestJS 如何连接到我们的数据库。这就像是为“智能图书管理系统”进行首次的安装和配置。
前置知识:ConfigModule
我们绝不会把数据库密码等敏感信息硬编码。所以,配置数据库连接的最佳实践是结合我们之前学过的 @nestjs/config
。
.env
文件
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=myuser
DB_PASSWORD=mypassword
DB_DATABASE=mydatabase```
**`src/app.module.ts`**
```typescript
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }), // 确保 ConfigModule 是全局的
// 使用 forRootAsync 来异步配置,因为它需要等待 ConfigService 就绪
TypeOrmModule.forRootAsync({
imports: [ConfigModule], // 导入 ConfigModule
inject: [ConfigService], // 注入 ConfigService
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get<string>('DB_HOST'),
port: configService.get<number>('DB_PORT'),
username: configService.get<string>('DB_USERNAME'),
password: configService.get<string>('DB_PASSWORD'),
database: configService.get<string>('DB_DATABASE'),
entities: [__dirname + '/../**/*.entity{.ts,.js}'], // 关键!自动加载实体
synchronize: true, // 开发环境使用,自动根据实体创建数据库表(生产环境慎用!)
}),
}),
],
})
export class AppModule {}
forRootAsync
: 因为数据库的配置依赖于ConfigService
,而ConfigService
的加载也需要时间,所以我们必须使用异步工厂的方式来配置。entities
: 这是 TypeORM 的核心概念之一。它告诉 TypeORM 去哪里寻找你定义的“实体类”。synchronize: true
: 这是一个非常方便的开发工具。它会在每次应用启动时,自动比较你的实体定义和数据库结构,并自动创建或修改数据表。警告: 在生产环境中,这可能会导致数据丢失,生产环境应该使用数据库迁移 (Migrations)。
2. 第二步:定义“书籍卡片” (Entity)
实体 (Entity) 是一个映射到数据库表的类。你可以把它想象成图书馆里每本书的“索引卡片”,上面定义了这本书有哪些属性(书名、作者、ISBN 等)。
src/cats/entities/cat.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
// @Entity() 装饰器将这个类标记为一个 TypeORM 实体
@Entity()
export class Cat {
// @PrimaryGeneratedColumn() 标记一个自增的主键列
@PrimaryGeneratedColumn()
id: number;
// @Column() 标记一个普通的数据列
@Column()
name: string;
@Column()
age: number;
@Column({ length: 100 }) // 可以传递选项,比如长度
breed: string;
}
```现在,当你启动应用时,`TypeORM` 会自动在你的数据库中创建一张名为 `cat` 的表,它包含 `id`, `name`, `age`, `breed` 这几个列。
### 3. 第三步:为特定“书架”创建管理员 (Repository)
有了数据库连接,有了实体定义,现在我们需要一个专门的工具来操作 `cat` 这张表。这个工具就是 **Repository (仓库)**。
你可以把 Repository 想象成某个特定书架(比如“科幻小说区”)的专属管理员。他只负责这个书架上的书,并提供一套标准的操作方法,如 `find()` (查找所有书), `findOne()` (找一本书), `save()` (放一本新书或更新信息), `remove()` (拿走一本书)。
**如何在模块中使用 Repository?**
我们需要在一个特性模块(比如 `CatsModule`)中,使用 `TypeOrmModule.forFeature()` 来注册我们想要使用的实体。
**`src/cats/cats.module.ts`**
```typescript
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CatsService } from './cats.service';
import { CatsController } from './cats.controller';
import { Cat } from './entities/cat.entity'; // 导入实体
@Module({
imports: [
// 使用 forFeature 注册 Cat 实体
// 这会让 CatRepository 在这个模块的作用域内可用
TypeOrmModule.forFeature([Cat]),
],
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
4. 第四步:在服务中注入并使用 Repository
现在,我们终于可以在 CatsService
中注入 CatRepository
,并用它来操作数据库了!
src/cats/cats.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Cat } from './entities/cat.entity';
import { CreateCatDto } from './dto/create-cat.dto';
@Injectable()
export class CatsService {
// 使用 @InjectRepository() 装饰器来注入 Cat 的 Repository
constructor(
@InjectRepository(Cat)
private readonly catsRepository: Repository<Cat>
) {}
// 创建一只猫 (Create)
async create(createCatDto: CreateCatDto): Promise<Cat> {
const cat = this.catsRepository.create(createCatDto); // 创建实体实例
return this.catsRepository.save(cat); // 保存到数据库
}
// 查找所有猫 (Read All)
async findAll(): Promise<Cat[]> {
return this.catsRepository.find();
}
// 查找一只猫 (Read One)
async findOne(id: number): Promise<Cat> {
const cat = await this.catsRepository.findOneBy({ id });
if (!cat) {
throw new NotFoundException(`Cat with ID ${id} not found`);
}
return cat;
}
// 删除一只猫 (Delete)
async remove(id: number): Promise<void> {
await this.catsRepository.delete(id);
}
}
看到了吗?我们的服务代码变得非常清晰和具有表现力。我们完全没有写一行 SQL 语句,而是通过调用 this.catsRepository
上语义明确的方法 (find
, create
, save
) 来完成所有数据库操作。
总结
将 NestJS 与数据库集成,遵循一个清晰的四步流程:
- 全局配置 (
forRootAsync
):在根模块中,使用@nestjs/typeorm
结合@nestjs/config
来配置好全局的数据库连接。 - 定义实体 (
@Entity
):创建 TypeScript 类,并使用 TypeORM 的装饰器将其映射到数据库表。 - 特性模块注册 (
forFeature
):在需要操作某个实体的特性模块中,使用TypeOrmModule.forFeature([MyEntity])
来注册它。 - 注入并使用仓库 (
@InjectRepository
):在服务中,通过@InjectRepository(MyEntity)
注入对应的Repository
,然后调用其方法来执行 CRUD 操作。
这个模式将数据库的底层细节与你的业务逻辑完美地分离开来,让你的代码更易于编写、阅读和测试。掌握了这个流程,就等于为你的 NestJS 应用装上了一颗强大、可靠的“记忆硬盘”。