NestJS 数据库集成:TypeORM、Sequelize 与 MongoDB 深入解析与实践

2,376 阅读14分钟

NestJS 数据库技术全解析

大家好,我是一名热衷于分享知识的软件开发者。今天,我想和大家深入探讨一下 NestJS 中的数据库技术。在我们的开发过程中,数据库无疑是一个至关重要的部分,它是我们存储和管理数据的核心。NestJS 作为一款高效、灵活的 Node.js 框架,为我们提供了多种集成数据库的方式。接下来,我将为大家详细介绍这些方法。

TypeORM 集成

首先,我们来看看如何在 NestJS 中集成 TypeORM。TypeORM 是一个强大的 ORM 框架,支持多种数据库,如 MySQL、PostgreSQL、MariaDB、SQLite 等。

什么是 TypeORM?

TypeORM 是一个强大的 ORM 框架,支持多种数据库,如 MySQL、PostgreSQL、MariaDB、SQLite 等。它是用 TypeScript 编写的,因此与 NestJS 框架集成得很好。

如何在 NestJS 中集成 TypeORM?

要在 NestJS 中使用 TypeORM,我们首先需要安装 @nestjs/typeorm 包和相应的数据库驱动。这可以通过运行以下命令完成:

npm install --save @nestjs/typeorm typeorm mysql

在这个示例中,我们选择了 MySQL 作为我们的数据库,因此我们安装了 mysql 驱动。如果你选择了其他的数据库,你需要安装相应的驱动。

安装完成后,我们可以将 TypeORM 模块导入到我们的根模块中。这可以通过在根模块中使用 TypeOrmModule.forRoot() 方法完成。这个方法接收一个参数,这个参数是一个对象,包含了数据库的配置信息。

以下是一个示例:

// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
 imports: [
   TypeOrmModule.forRoot({
   type: 'mysql', // 数据库类型
   host: 'localhost', // 数据库主机名
   port: 3306, // 数据库端口
   username: 'root', // 数据库用户名
   password: 'root', // 数据库密码
   database: 'test', // 数据库名
   entities: [__dirname + '/**/*.entity{.ts,.js}'], // 实体位置
   synchronize: true, // 是否自动同步数据库结构,慎用
   }),
 ],
})
export class AppModule {}

定义实体

在 TypeORM 中,实体是一个映射到数据库表的类。我们可以使用装饰器来定义实体和实体的属性。

以下是一个定义 Cat 实体的示例:

// cat.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Cat {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  age: number;
}

在这个示例中,我们首先导入了 EntityColumnPrimaryGeneratedColumn 装饰器。然后,我们定义了一个 Cat 类,并使用 @Entity() 装饰器来将这个类标记为实体。我们还定义了三个属性:idnameage,并使用 @PrimaryGeneratedColumn()@Column() 装饰器来定义这些属性的类型和是否为主键。

创建服务

在我们的服务中,我们可以通过注入 Repository 来使用 TypeORM。Repository 是 TypeORM 提供的一个类,它包含了一些用于操作数据库的方法。

以下是一个创建 CatService 的示例:

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

@Injectable()
export class CatService {
  constructor(
    @InjectRepository(Cat)
    private catRepository: Repository<Cat>,
  ) {}

  findAll(): Promise<Cat[]> {
    return this.catRepository.find();
  }
}

在这个示例中,我们首先导入了 InjectRepository 装饰器和 Repository 类。然后,我们在 CatService 的构造函数中注入了 Cat 实体的 Repository。最后,我们在 findAll 方法中使用了 catRepository.find() 来获取所有的猫。

创建控制器

在 NestJS 中,控制器负责处理传入的 HTTP 请求。我们可以在控制器中注入我们的服务,然后在路由处理函数中调用服务的方法。

以下是一个创建 CatController 的示例:

// cat.controller.ts
import { Controller, Get } from '@nestjs/common';
import { CatService } from './cat.service';
import { Cat } from './cat.entity';

@Controller('cats')
export class CatController {
  constructor(private catService: CatService) {}

  @Get()
  findAll(): Promise<Cat[]> {
    return this.catService.findAll();
  }
}

在这个示例中,我们首先导入了 ControllerGet 装饰器,以及我们之前创建的 CatServiceCat 实体。然后,我们定义了一个 CatController 类,并使用 @Controller('cats') 装饰器来定义这个类的路由前缀。我们还在构造函数中注入了 CatService。最后,我们定义了一个 findAll 方法,并使用 @Get() 装饰器来定义这个方法处理 GET 请求。

如何使用 TypeORM?

在我们的服务中,我们可以通过注入 Repository 来使用 TypeORM。Repository 是 TypeORM 提供的一个类,它包含了一些用于操作数据库的方法。

以下是一个示例:

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

@Injectable()
export class CatService {
  constructor(
    @InjectRepository(Cat)
    private catRepository: Repository<Cat>,
  ) {}

  findAll(): Promise<Cat[]> {
    return this.catRepository.find();
  }
}

在这个示例中,我们首先导入了 InjectRepository 装饰器和 Repository 类。然后,我们在 CatService 的构造函数中注入了 Cat 实体的 Repository。最后,我们在 findAll 方法中使用了 catRepository.find() 来获取所有的猫。

如何使用 TypeORM 迁移数据库?

在实际开发过程中,我们的数据库结构可能会随着需求的变化而变化。这时,我们就需要进行数据库迁移。TypeORM 提供了一套强大的迁移工具,我们可以使用这些工具来管理我们的数据库迁移。

首先,我们需要创建一个迁移文件。我们可以使用 TypeORM 的 CLI 工具来创建迁移文件。以下是一个创建迁移文件的命令:

npx typeorm migration:create -n CatMigration

在这个命令中,CatMigration 是我们的迁移名称。运行这个命令后,TypeORM 将在我们的 migrations 目录下创建一个新的迁移文件。

然后,我们可以在这个迁移文件中定义我们的迁移逻辑。以下是一个迁移文件的示例:

// 1632390741234-CatMigration.ts
import {MigrationInterface, QueryRunner} from "typeorm";

export class CatMigration1632390741234 implements MigrationInterface {
    name = 'CatMigration1632390741234'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "cat" ADD "breed" varchar(255)`);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "cat" DROP COLUMN "breed"`);
    }
}

在这个示例中,我们在 up 方法中添加了一个新的列 breed,在 down 方法中删除了这个列。up 方法是当我们运行迁移时会被调用的方法,down 方法是当我们回滚迁移时会被调用的方法。

最后,我们可以使用 TypeORM 的 CLI 工具来运行我们的迁移。以下是一个运行迁移的命令:

npx typeorm migration:run

运行这个命令后,TypeORM 将会运行我们所有未运行的迁移。

如果我们需要回滚我们的迁移,我们可以使用以下命令: npx typeorm migration:revert

npx typeorm migration:revert

运行这个命令后,TypeORM 将会回滚我们最近运行的迁移。

以上就是如何使用 TypeORM 迁移数据库的基本步骤。在实际开发中,我们可能需要根据我们的需求来编写更复杂的迁移逻辑。

Sequelize 集成

接下来,我们来看看如何在 NestJS 中集成 Sequelize。

什么是 Sequelize?

Sequelize 是一个基于 promise 的 Node.js ORM,用于 Postgres、MySQL、MariaDB、SQLite 和 Microsoft SQL Server。它支持事务、关联、复制、读取和写入分离等功能。

如何在 NestJS 中集成 Sequelize?

要在 NestJS 中使用 Sequelize,我们首先需要安装 @nestjs/sequelize 包和相应的数据库驱动。这可以通过运行以下命令完成:

npm install --save @nestjs/sequelize sequelize mysql2

在这个示例中,我们选择了 MySQL 作为我们的数据库,因此我们安装了 mysql2 驱动。如果你选择了其他的数据库,你需要安装相应的驱动。

安装完成后,我们可以将 Sequelize 模块导入到我们的根模块中。这可以通过在根模块中使用 SequelizeModule.forRoot() 方法完成。这个方法接收一个参数,这个参数是一个对象,包含了数据库的配置信息。

以下是一个示例:

// app.module.ts
import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';

@Module({
 imports: [
   SequelizeModule.forRoot({
   dialect: 'mysql', // 数据库类型
   host: 'localhost', // 数据库主机名
   port: 3306, // 数据库端口
   username: 'root', // 数据库用户名
   password: 'root', // 数据库密码
   database: 'test', // 数据库名
   models: [__dirname + '/**/*.model{.ts,.js}'], // 模型位置
   autoLoadModels: true, // 是否自动加载模型
   }),
 ],
})
export class AppModule {}

定义模型

在 Sequelize 中,模型是一个映射到数据库表的类。我们可以使用装饰器来定义模型和模型的属性。

以下是一个定义 Cat 模型的示例:

// cat.model.ts
import { Column, DataType, Model, Table } from 'sequelize-typescript';

@Table
export class Cat extends Model {
  @Column({
    type: DataType.INTEGER,
    primaryKey: true,
    autoIncrement: true,
  })
  id: number;

  @Column({
    type: DataType.STRING,
  })
  name: string;

  @Column({
    type: DataType.INTEGER,
  })
  age: number;
}

在这个示例中,我们首先导入了 ColumnDataTypeModelTable 装饰器。然后,我们定义了一个 Cat 类,并使用 @Table 装饰器来将这个类标记为模型。我们还定义了三个属性:idnameage,并使用 @Column 装饰器来定义这些属性的类型和其他选项。

创建服务

在我们的服务中,我们可以通过注入 Model 来使用 Sequelize。Model 是 Sequelize 提供的一个类,它包含了一些用于操作数据库的方法。

以下是一个创建 CatService 的示例:

// cat.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { Cat } from './cat.model';

@Injectable()
export class CatService {
  constructor(
    @InjectModel(Cat)
    private catModel: typeof Cat,
  ) {}

  findAll(): Promise<Cat[]> {
    return this.catModel.findAll();
  }
}

在这个示例中,我们首先导入了 InjectModel 装饰器和 Cat 模型。然后,我们在 CatService 的构造函数中注入了 Cat 模型。最后,我们在 findAll 方法中使用了 catModel.findAll() 来获取所有的猫。

创建控制器

在 NestJS 中,控制器负责处理传入的 HTTP 请求。我们可以在控制器中注入我们的服务,然后在路由处理函数中调用服务的方法。

以下是一个创建 CatController 的示例:

// cat.controller.ts
import { Controller, Get } from '@nestjs/common';
import { CatService } from './cat.service';
import { Cat } from './cat.model';

@Controller('cats')
export class CatController {
  constructor(private catService: CatService) {}

  @Get()
  findAll(): Promise<Cat[]> {
    return this.catService.findAll();
  }
}

在这个示例中,我们首先导入了 ControllerGet 装饰器,以及我们之前创建的 CatServiceCat 模型。然后,我们定义了一个 CatController 类,并使用 @Controller('cats') 装饰器来定义这个类的路由前缀。我们还在构造函数中注入了 CatService。最后,我们定义了一个 findAll 方法,并使用 @Get() 装饰器来定义这个方法处理 GET 请求。

Mongoose 集成

对于 MongoDB 数据库,Nest 提供了 @nestjs/mongoose 包。

什么是 MongoDB?

MongoDB 是一个基于分布式文件存储的开源数据库系统。在 NoSQL 数据库中,MongoDB 以其卓越的性能,丰富的查询特性,伸缩性以及地理空间特性等优点,使其成为 NoSQL 数据库中的翘楚。

如何在 NestJS 中集成 MongoDB?

要在 NestJS 中使用 MongoDB,我们首先需要安装 @nestjs/mongoose 包和 mongoose。这可以通过运行以下命令完成:

npm install --save @nestjs/mongoose mongoose

安装完成后,我们可以将 Mongoose 模块导入到我们的根模块中。这可以通过在根模块中使用 MongooseModule.forRoot() 方法完成。这个方法接收一个参数,这个参数是 MongoDB 的连接字符串。

以下是一个示例:

// app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
 imports: [
   MongooseModule.forRoot('mongodb://localhost/test'),
 ],
})
export class AppModule {}

定义模型

在 Mongoose 中,模型是一个映射到 MongoDB 集合的类,并绑定了我们定义的模式。我们可以使用装饰器来定义模型和模型的属性。

以下是一个定义 Cat 模型的示例:

// cat.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

@Schema()
export class Cat extends Document {
  @Prop()
  name: string;

  @Prop()
  age: number;
}
export const CatSchema = SchemaFactory.createForClass(Cat);

在这个示例中,我们首先导入了 PropSchemaSchemaFactory 装饰器和 Document 类。然后,我们定义了一个 Cat 类,并使用 @Schema() 装饰器来将这个类标记为模型。我们还定义了两个属性:nameage,并使用 @Prop() 装饰器来定义这些属性。

创建服务

在我们的服务中,我们可以通过注入 Model 来使用 Mongoose。Model 是 Mongoose 提供的一个类,它包含了一些用于操作数据库的方法。

以下是一个创建 CatService 的示例:

// cat.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Cat, CatDocument } from './cat.schema';

@Injectable()
export class CatService {
  constructor(
    @InjectModel(Cat.name)
    private catModel: Model<CatDocument>,
  ) {}

  findAll(): Promise<Cat[]> {
    return this.catModel.find().exec();
  }
}

在这个示例中,我们首先导入了 InjectModel 装饰器和 Model 类。然后,我们在 CatService 的构造函数中注入了 Cat 模型。最后,我们在 findAll 方法中使用了 catModel.find().exec() 来获取所有的猫。

创建控制器

在 NestJS 中,控制器负责处理传入的 HTTP 请求。我们可以在控制器中注入我们的服务,然后在路由处理函数中调用服务的方法。

以下是一个创建 CatController 的示例:

// cat.controller.ts
import { Controller, Get } from '@nestjs/common';
import { CatService } from './cat.service';
import { Cat } from './cat.schema';

@Controller('cats')
export class CatController {
  constructor(private catService: CatService) {}

  @Get()
  findAll(): Promise<Cat[]> {
    return this.catService.findAll();
  }
}

在这个示例中,我们首先导入了 ControllerGet 装饰器,以及我们之前创建的 CatServiceCat 模型。然后,我们定义了一个 CatController 类,并使用 @Controller('cats') 装饰器来定义这个类的路由前缀。我们还在构造函数中注入了 CatService。最后,我们定义了一个 findAll 方法,并使用 @Get() 装饰器来定义这个方法处理 GET 请求。

自定义数据库集成

如果你选择的数据库或 ORM 没有在上述列表中,或者你想要更多的控制权,你可以自己创建一个自定义提供者来集成你的数据库。这种情况下,你需要自己处理连接和断开连接的逻辑,以及如何将数据库连接或客户端暴露给你的服务。

么是自定义数据库集成?

在某些情况下,你可能会选择一个不在 NestJS 默认支持列表中的数据库,或者你可能需要更多的控制权,这时候,你就需要进行自定义数据库集成。自定义数据库集成意味着你需要自己处理连接和断开连接的逻辑,以及如何将数据库连接或客户端暴露给你的服务。

如何进行自定义数据库集成?

首先,你需要安装你选择的数据库的 Node.js 驱动程序。然后,你可以创建一个自定义提供者来集成你的数据库。在 NestJS 中,提供者是一个可以注入到其他类中的类、工厂或值。在我们的情况下,我们的提供者将是一个工厂,它将创建数据库连接。

以下是一个创建自定义数据库提供者的示例:

// database.providers.ts
import { createConnection } from 'some-db-client';
import { DATABASE_CONNECTION } from './database.constants';

export const databaseProviders = [
  {
    provide: DATABASE_CONNECTION,
    useFactory: async () => await createConnection({
      // database options
    }),
  },
];

在这个示例中,我们首先导入了我们的数据库客户端,并定义了一个常量 DATABASE_CONNECTION,这将是我们的提供者的令牌。然后,我们创建了一个提供者数组,其中包含一个提供者。这个提供者有一个 provide 属性,它的值是我们的令牌,还有一个 useFactory 属性,它的值是一个异步工厂函数,这个函数将创建我们的数据库连接。

接下来,我们需要在我们的模块中导入这个提供者。我们可以在根模块中做这个,也可以在任何其他模块中做这个,只要这个模块需要使用数据库连接。

// app.module.ts
import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';

@Module({
  imports: [...databaseProviders],
})
export class AppModule {}

在这个示例中,我们导入了我们之前创建的 databaseProviders,然后在 imports 数组中包含了它。

最后,我们可以在我们的服务中注入我们的数据库连接。我们只需要在构造函数中添加一个参数,这个参数的装饰器是 @Inject,并且它的参数是我们的提供者令牌。

// some.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { DATABASE_CONNECTION } from './database.constants';

@Injectable()
export class SomeService {
  constructor(@Inject(DATABASE_CONNECTION) private dbConnection) {}

  findAll() {
    // use this.dbConnection to query the database
  }
}

在这个示例中,我们在 SomeService 的构造函数中注入了我们的数据库连接。然后,我们可以在 findAll 方法中使用 this.dbConnection 来查询数据库。 自定义数据库集成为我们提供了更大的灵活性,让我们可以根据自己的需求选择最适合的数据库,并自行控制数据库的连接和断开连接。虽然这需要我们投入更多的时间和精力,但是对于那些需要更多控制权或使用特殊数据库的项目来说,这是非常值得的。

总结

在软件开发的世界中,数据库是我们无法绕过的一环。无论是关系型数据库,还是非关系型数据库,它们都在我们的应用中扮演着至关重要的角色。因此,如何在我们的应用中有效地集成和使用数据库,是我们每一个开发者都需要掌握的技能。

在 NestJS 中,我们有多种方式可以集成数据库。无论你选择使用 TypeORM、Sequelize 还是 Mongoose,NestJS 都为你提供了一套简单、直观的 API,让你可以轻松地在你的应用中使用这些 ORM 框架。

笔者在日常开发中使用typeorm居多。