从零入门 NestJS:构建你的第一个 REST API(六)连接数据库

339 阅读5分钟

连接数据库

在实际项目中,数据的持久化和管理是后端开发的核心环节。NestJS 推荐使用 TypeORM 作为 ORM 框架,支持多种主流数据库(如 MySQL、PostgreSQL、SQLite 等),并与 NestJS 的依赖注入、模块化机制深度集成。

通过 ORM,开发者可以用面向对象的方式操作数据库,避免了繁琐的 SQL 拼接和手动管理连接池等底层细节。TypeORM 让你专注于业务逻辑,提升开发效率和代码可维护性。

TypeORM 简介

TypeORM 是一款基于 TypeScript 的 ORM 框架,支持实体(Entity)与数据库表的映射,提供声明式的数据库操作方式。它让开发者可以像操作对象一样操作数据库,极大提升开发效率和代码可维护性。

  • 支持多种数据库:MySQL、PostgreSQL、SQLite、MongoDB 等
  • 支持实体、关系、迁移、查询构建器等高级特性
  • 与 NestJS 无缝集成,支持依赖注入和模块化

TypeORM 的核心思想是"实体即表",每个实体类对应数据库中的一张表,类的属性对应表的字段。通过装饰器声明字段类型、主键、索引、关系等,代码即文档,结构一目了然。

环境准备与数据库配置

  1. 安装依赖:
npm install --save @nestjs/typeorm typeorm mysql2
  1. app.module.ts 中配置 TypeORM:
// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from './user/user.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'your_password',
      database: 'test_db',
      autoLoadEntities: true, // 自动加载实体
      synchronize: true,      // 开发环境下自动建表,生产环境建议关闭
    }),
    UserModule,
  ],
})
export class AppModule {}

synchronize: true 适合开发阶段,生产环境建议用迁移工具管理表结构。

定义实体(Entity)

实体是 TypeORM 中用于描述数据库表结构的类。每个实体对应一张表,类的属性对应表的字段。通过装饰器可以声明主键、唯一约束、默认值、关系等。

// user/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Post } from '../post/post.entity';

@Entity('user')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 20 })
  username: string;

  @Column()
  password: string;

  @Column({ default: true })
  isActive: boolean;

  @OneToMany(() => Post, post => post.user)
  posts: Post[]; // 用户的所有文章
}

通过关系装饰器(如 @OneToMany、@ManyToOne)可以轻松实现连表查询。

Repository 的作用

TypeORM 的 Repository 是操作实体的核心对象,封装了常用的增删改查方法(如 save、find、findOne、update、delete 等),也支持复杂的查询构建。

在 NestJS 中,Repository 通过 @InjectRepository 注入到 Service 层,结合依赖注入机制,保证了代码的解耦和可测试性。

集成 TypeORM 模块

在业务模块中引入 TypeORM 的 TypeOrmModule.forFeature,并在 Service 层注入 Repository 进行数据库操作。

// user/user.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UserService } from './user.service';
import { UserController } from './user.controller';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UserService],
  controllers: [UserController],
  exports: [UserService],
})
export class UserModule {}

实现基础 CRUD 操作

// user/user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepo: Repository<User>,
  ) {}

  async create(username: string, password: string) {
    const user = this.userRepo.create({ username, password });
    return this.userRepo.save(user);
  }

  async findAll() {
    return this.userRepo.find();
  }

  async findById(id: number) {
    return this.userRepo.findOneBy({ id });
  }

  async update(id: number, data: Partial<User>) {
    await this.userRepo.update(id, data);
    return this.findById(id);
  }

  async remove(id: number) {
    return this.userRepo.delete(id);
  }
}

连表查询(关系与 QueryBuilder)

在实际业务中,常常需要查询用户及其关联的其他数据(如文章、评论等)。TypeORM 支持通过 relations 选项或 QueryBuilder 实现连表查询。

1. 使用 relations 选项

// 查询用户及其所有文章
async findUserWithPosts(id: number) {
  return this.userRepo.findOne({
    where: { id },
    relations: ['posts'], // 关联查询
  });
}

2. 使用 QueryBuilder

QueryBuilder 提供了更灵活的 SQL 构建能力,适合复杂的多表关联、条件筛选等场景。

import { getRepository } from 'typeorm';

async function findActiveUsersWithPosts() {
  return getRepository(User)
    .createQueryBuilder('user')
    .leftJoinAndSelect('user.posts', 'post')
    .where('user.isActive = :active', { active: true })
    .getMany();
}

事务(Transaction)处理

在涉及多步数据库操作时,事务可以保证数据的一致性和原子性。TypeORM 支持多种事务写法。

1. 使用 manager 进行手动事务

import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';

@Injectable()
export class UserService {
  constructor(private dataSource: DataSource) {}

  async transferPoints(fromUserId: number, toUserId: number, points: number) {
    return this.dataSource.transaction(async manager => {
      const fromUser = await manager.findOne(User, { where: { id: fromUserId } });
      const toUser = await manager.findOne(User, { where: { id: toUserId } });
      if (!fromUser || !toUser) throw new Error('用户不存在');
      if (fromUser.points < points) throw new Error('积分不足');
      fromUser.points -= points;
      toUser.points += points;
      await manager.save(fromUser);
      await manager.save(toUser);
    });
  }
}

2. 使用 @Transaction 装饰器(适用于较老版本 TypeORM)

推荐优先使用 DataSource 的 transaction 方法,@Transaction 装饰器已不再推荐。

Controller 示例

// user/user.controller.ts
import { Controller, Post, Body, Get, Param, Put, Delete } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post('register')
  async register(@Body() body: { username: string; password: string }) {
    return this.userService.create(body.username, body.password);
  }

  @Get(':id')
  async getUser(@Param('id') id: number) {
    return this.userService.findById(id);
  }

  @Get()
  async list() {
    return this.userService.findAll();
  }

  @Put(':id')
  async update(@Param('id') id: number, @Body() body: any) {
    return this.userService.update(id, body);
  }

  @Delete(':id')
  async remove(@Param('id') id: number) {
    return this.userService.remove(id);
  }
}

实战场景:用户信息的持久化

通过上述结构,用户注册、查询、更新、删除等操作都可以通过 RESTful API 实现,并自动映射到数据库表。这样不仅提升了开发效率,也保证了数据一致性和安全性。

在实际项目中,常见的业务场景还包括:

  • 用户与订单、文章等多表关联的查询与统计
  • 复杂的筛选、分页、排序等需求
  • 需要保证一致性的积分转账、余额扣减等事务操作

TypeORM 的丰富特性可以帮助你优雅地应对这些需求。


最佳实践与常见坑点

  • 实体字段类型要与数据库一致,否则可能导致运行时错误。
  • 生产环境关闭 synchronize,使用 TypeORM migration 管理表结构。
  • Repository 注入要用 @InjectRepository,否则依赖注入会失败。
  • DTO 校验与实体分离,避免直接暴露数据库结构。
  • 错误处理要完善,如用户不存在时返回 404。
  • 避免 N+1 查询,可用 relations 或 QueryBuilder 优化。
  • 事务操作要捕获异常,保证回滚,避免数据不一致。
  • 建议统一管理数据库连接配置,便于多环境切换和维护。

连接数据库是后端开发的基础,建议多查阅 TypeORM 官方文档 和 NestJS 数据库集成文档,掌握更多高级用法。