如何使用 TypeORM 向 NestJS API 添加免费的 MongoDB 数据库

2025-06-04

如何使用 TypeORM 向 NestJS API 添加免费的 MongoDB 数据库

如果您正在为您的应用程序构建 API,您可能会问自己的第一个问题之一是将我的数据存储在哪里?

大多数情况下,答案都在数据库中,但究竟选择哪一个呢?尤其如果您正在寻找价格低廉(甚至免费)且性能良好的数据库,那么您的选择就相当有限了。好消息是,Azure Cosmos DB 推出了免费套餐,适用于生产工作负载,最高可提供 5 GB 的存储空间。

在本文中,我们将介绍在新的NestJS应用程序中配置和使用Azure Cosmos DB数据库的所有步骤

TL;DR 关键要点

  • NestJS 提供了出色的 TypeORM 集成,大大简化了数据库的使用。
  • Azure Cosmos DB 可以与包括 MongoDB 在内的许多不同的驱动程序一起使用,从而可以轻松地与现有库集成。
  • 虽然 TypeORM 主要关注 SQL 数据库,但它也可以与使用 MongoDB 的 NoSQL 很好地配合。

这是GitHub 上的最终项目源代码

您将在这里学到什么?

在本文中,我们将:

  • 使用 NestJS 从头开始​​引导 Node.js API
  • 创建支持 MongoDB 的免费 Azure Cosmos DB 数据库
  • 配置 TypeORM 以进行 MongoDB 连接
  • 创建一个实体来映射你的模型和数据库
  • 将 CRUD 端点添加到您的 NestJS 应用

我们使用的所有东西的参考链接

要求

或者,如果您不想创建 Azure 订阅,您也可以使用试用 Cosmos DB网站来访问 Cosmos DB 试用实例。

入门

您的 API 将使用NestJS构建

如果您不熟悉 NestJS,它是一个TypeScript Node.js 框架,看起来很像 Angular,可以帮助您构建企业级高效且可扩展的 Node.js 应用程序。

安装 NestJS CLI 并引导新的服务器应用程序

使用以下命令安装 NestJS CLI 并创建一个新的应用程序:

$ npm install -g @nestjs/cli
$ nest new pets-api
$ cd pets-api
Enter fullscreen mode Exit fullscreen mode

我们将创建一个简单的宠物管理 API 作为示例,因此让我们使用以下命令为其创建一个控制器:

$ nest generate controller pets
Enter fullscreen mode Exit fullscreen mode

您现在可以集成数据库了。

配置 Cosmos DB

Cosmos DB是一个托管的分布式 NoSQL 数据库,可用于保存和检索数据。它支持多种数据模型和许多知名的数据库 API,包括我们将在应用程序中使用的MongoDB 。

CosmosDB 多模型和不同 API 说明

首先,我们必须创建一个 Cosmos DB 帐户,该帐户可以容纳一个或多个数据库。在执行以下步骤之前,请确保你拥有Azure 帐户:

  1. 单击此链接:创建 Azure Cosmos DB 帐户。​​如有需要,请登录,然后按如下方式填写表单:

    mongoDB 数据库创建选项

    完成后,单击“审阅 + 创建”,最后单击“创建”

  2. 配置数据库需要几分钟时间,因此您可以继续下一部分,完成后再回来。准备就绪后,点击“转到资源”

  3. 单击“数据资源管理器”选项卡,然后单击“新建集合”按钮:

    数据探索器的屏幕截图

  4. 像这样填写字段:

    新收藏集创建的屏幕截图

    这里有两件事值得一提:

    • 我们选择使用复选框在数据库内的所有集合之间共享预配置的请求单位吞吐量Provision database throughput。这极大地有助于在使用付费帐户时降低成本。
    • 我们需要为集合定义一个分片键(也称为分区键),以确保正确的分区_id。我们使用MongoDB默认的自动生成属性。
  5. 最后,转到Connection strings选项卡并单击主连接字符串旁边的按钮进行复制:

    连接字符串的屏幕截图

现在.env在项目根目录中创建一个具有以下值的文件:

MONGODB_CONNECTION_STRING=<your primary connection string>
MONGODB_DATABASE=pets-db
Enter fullscreen mode Exit fullscreen mode

注意:切勿将该.env文件提交到你的仓库!此文件仅用于本地测试,因此请将其添加到你的.gitignore文件中。

在开发过程中,这些值将作为环境变量暴露给你的应用,以便访问你的数据库。为此,我们使用@nestjs/config提供dotenv集成的包:

npm i @nestjs/config
Enter fullscreen mode Exit fullscreen mode

打开文件src/app.module.ts并将其添加到模块导入:

...
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot(),
    ...
  ]
Enter fullscreen mode Exit fullscreen mode

这就是我们目前所需要的,但请记住,它为高级需求@nestjs/config提供了更多的选择。

与 NestJS 集成

现在,您可以在应用程序中使用数据库了。NestJS 与TypeORM提供了良好的集成,TypeORM 是 TypeScript 中最成熟的对象关系映射器 (ORM),因此我们将使用它。

首先,您必须使用此命令安装更多软件包:

npm install @nestjs/typeorm typeorm mongodb
Enter fullscreen mode Exit fullscreen mode

打开文件src/app.module.ts并添加TypeOrmModule到模块导入:

import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mongodb',
      url: process.env.MONGODB_CONNECTION_STRING,
      database: process.env.MONGODB_DATABASE,
      entities: [
        __dirname + '/**/*.entity{.ts,.js}',
      ],
      ssl: true,
      useUnifiedTopology: true,
      useNewUrlParser: true
    }),
    ...
  ]
Enter fullscreen mode Exit fullscreen mode

提示:使用process.env.<VARIABLE_NAME>代替硬编码值可以将敏感信息保留在代码库之外,而是从环境变量中读取。这也允许您在不同环境(例如,暂存和生产)上部署完全相同的代码,但使用不同的配置,正如12 要素应用最佳实践中所建议的那样。

TypeORM 将根据模块选项中指定的*.entity.ts(一旦编译)命名方案发现并映射您的实体。.js

但是,我们还没有实体吗?没错,让我们创建它!

创建实体

数据库实体用于建模您想要存储的任何对象的属性。在我们的例子中,我们想要存储宠物数据,因此让我们定义一个Pet实体。

src/pets/pet.entity.ts使用以下代码创建一个新文件:

import { Entity, ObjectID, ObjectIdColumn, Column } from 'typeorm';

@Entity('pets')
export class Pet {
  @ObjectIdColumn() id: ObjectID;
  @Column() name: string;
  @Column() animalType: string;
  @Column() pictureUrl?: string;
  @Column() birthDate?: Date;

  constructor(pet?: Partial<Pet>) {
    Object.assign(this, pet);
  }
}
Enter fullscreen mode Exit fullscreen mode

现在让我们分解一下我们使用过的注释:

  • @Entity将该类标记为要存储到集合中的 TypeORM 实体pets
  • @ObjectIdColumn标记将映射到必需 MongoDB 属性的实体的唯一标识符_id。如果您未提供,它将自动生成。
  • @Column标记要映射到表列的属性。属性的类型还将定义要存储的数据类型。

注意:对于更复杂的域模型,您可以使用简单类型引用定义子文档,请参阅此示例

注入存储库

TypeORM 支持存储库设计模式,并且@nestjs/typeorm包为您提供了一种为每个实体声明可注入存储库的简单方法。

再次打开文件src/app.module.ts并将其添加到模块导入中:

import { Pet } from './pets/pet.entity';

@Module({
  imports: [
    TypeOrmModule.forFeature([Pet]),
    ...
  ]
Enter fullscreen mode Exit fullscreen mode

Pet现在,您可以使用注释注入存储库@InjectRepository。打开文件src/pets/pets.controller.ts并添加此构造函数:

@Controller('pets')
export class PetsController {
  constructor(
    @InjectRepository(Pet)
    private readonly petsRepository: MongoRepository<Pet>,
  ) {}
  ...
}
Enter fullscreen mode Exit fullscreen mode

不要忘记在文件顶部添加这些缺失的导入:

import { InjectRepository } from '@nestjs/typeorm';
import { MongoRepository } from 'typeorm';
import { ObjectID } from 'mongodb';
import { Pet } from './pet.entity';
Enter fullscreen mode Exit fullscreen mode

您现在可以this.petsRepository在控制器中使用它来执行 CRUD 操作(方法签名已简化以提高可读性):

  • save(entity: PartialEntity<Entity> | PartialEntity<Entity>[], options?: SaveOptions): Promise<Entity | Entity[]>:如果不存在,则在数据库中插入一个或多个实体,否则更新。
  • findOne(criteria?: ObjectID | FindOneOptions<Entity>): Promise<Entity | undefined>:查找第一个与 ID 或查询选项匹配的实体。
  • find(criteria?: FindManyOptions<Entity>): Promise<Entity[]>:查找符合指定条件的所有实体(如果未提供则返回所有实体)。
  • update(criteria: ObjectID | ObjectID[] | FindConditions<Entity>, partialEntity: PartialEntity<Entity> | PartialEntity<Entity>[]): Promise<UpdateResult>:更新符合指定条件的实体。它允许部分更新,但不检查实体是否存在。
  • delete(criteria: ObjectID | ObjectID[] | FindConditions<Entity>): Promise<DeleteResult>:从数据库中删除一个或多个符合指定条件的实体。不检查实体是否存在。

在所有这些方法中,您可以使用实体 ID 或常规MongoDB 查询来匹配特定实体。例如,您可以使用:

// Find all cats
await this.petsRepository.find({ animalType: 'cat' });

// Find the pet with the specified ID
await this.petsRepository.findOne(id);
Enter fullscreen mode Exit fullscreen mode

添加新端点

现在您已拥有创建 CRUD(创建、读取、更新和删除)端点所需的一切:

GET /pets         // Get all pets
GET /pets/:id     // Get the pet with the specified ID
POST /pets        // Create a new pet
PUT /pets/:id     // Update the pet with the specified ID
DELETE /pets/:id  // Delete the pet with the specified ID
Enter fullscreen mode Exit fullscreen mode

让我们从第一个开始,获取所有的宠物。将此方法添加到你的控制器中:

@Get()
async getPets(): Promise<Pet[]> {
  return await this.petsRepository.find();
}
Enter fullscreen mode Exit fullscreen mode

很简单吧😎?如果不指定该find()方法的任何条件,集合中的所有实体都将被返回。

现在继续下一个,使用其 ID 检索单个宠物:

@Get(':id')
async getPet(@Param('id') id): Promise<Pet> {
  const pet = ObjectID.isValid(id) && await this.petsRepository.findOne(id);
  if (!pet) {
    // Entity not found
    throw new NotFoundException();
  }
  return pet;
}
Enter fullscreen mode Exit fullscreen mode

我们像以前一样使用@Get()注解,但这次我们使用添加路由参数:id。然后可以使用@Param('id')注解通过函数参数检索此参数。

我们检查提供的字符串是否为有效的 MongoDB 字符串ObjectID,然后调用该petsRepository.findOne()方法查找匹配的实体。如果未找到或提供的 ID 无效,我们将404使用 NestJS 预定义的异常类返回状态错误NotFoundException

创造

现在开始创建宠物:

@Post()
async createPet(@Body() pet: Partial<Pet>): Promise<Pet> {
  if (!pet || !pet.name || !pet.animalType) {
    throw new BadRequestException(`A pet must have at least name and animalType defined`);
  }
  return await this.petsRepository.save(new Pet(pet));
}
Enter fullscreen mode Exit fullscreen mode

这里我们使用@Body()注解作为函数参数来检索宠物的请求数据。我们还400使用 NestJS 添加了基本验证并返回带有消息的状态错误BadRequestException

提示:有关使用 DTO(数据传输对象)和注释的更高级验证技术,您可以查看此文档

更新

对于更新端点,它是读取创建的混合

@Put(':id')
@HttpCode(204)
async updatePet(@Param('id') id, @Body() pet: Partial<Pet>): Promise<void> {
  // Check if entity exists
  const exists = ObjectID.isValid(id) && await this.petsRepository.findOne(id);
  if (!exists) {
    throw new NotFoundException();
  }
  await this.petsRepository.update(id, pet);
}
Enter fullscreen mode Exit fullscreen mode

我们添加了注释@HttpCode(204),将 HTTP 状态更改为204(No Content),因为如果更新成功,我们不会返回任何内容。我们还需要在更新实体之前检查它是否存在。

删除

最后,我们添加与前一个方法非常相似的删除方法:

@Delete(':id')
@HttpCode(204)
async deletePet(@Param('id') id): Promise<void> {
  // Check if entity exists
  const exists = ObjectID.isValid(id) && await this.petsRepository.findOne(id);
  if (!exists) {
    throw new NotFoundException();
  }
  await this.petsRepository.delete(id);
}
Enter fullscreen mode Exit fullscreen mode

CRUD 端点,完成✔️。

如果您想了解有关可在控制器中使用的可用注释和帮助程序的更多信息,可以查看NestJS 文档TypeORM 文档

测试您的端点

现在是时候测试您的 REST API 是否有效了,使用以下命令启动您的服务器:

npm run start
Enter fullscreen mode Exit fullscreen mode

当服务器启动时,您可以使用以下命令测试新端点是否正常运行curl

curl http://localhost:3000/pets
# should return an empty list: []

curl http://localhost:3000/pets/0
# should return 404 with an error

curl http://localhost:3000/pets \
  -X POST \
  -H "content-type: application/json" \
  -d '{ "name": "Garfield", "animalType": "cat" }'
# should return the newly created pet

curl http://localhost:3000/pets
# should return a list including the previously added pet

curl http://localhost:3000/pets/<id_from_post_command> \
  -X PUT \
  -H "content-type: application/json" \
  -d '{ "pictureUrl": "https://placekitten.com/400/600" }'
# should update the pet

curl http://localhost:3000/pets/<id_from_post_command>
# should return the updated pet

curl http://localhost:3000/pets/<id_from_post_command> \
  -X DELETE
# should delete the pet
Enter fullscreen mode Exit fullscreen mode

探索您的数据

一旦您使用 API 玩了一下并创建了一些宠物,为什么不看看您的数据呢?

您可以使用独立的存储资源管理器应用程序,也可以转到 Azure 门户并访问在线版本。

我们只想快速浏览一下,所以让我们使用在线版本:

  1. 返回portal.azure.com

  2. 使用顶部的搜索栏并输入您创建的 Cosmos DB 帐户的名称,然后在搜索结果中单击它:

    搜索你的 Cosmos DB 帐户

  3. 单击资源菜单中的存储资源管理器pets-db,然后展开数据库和pets集合以打开数据所在的文档:

    存储资源管理器

在这里,您可以查询您的宠物,编辑或删除它们,甚至创建新的宠物。
此工具可以帮助您快速直观地检查数据,并在出现问题时进行调试。

进一步

这是一个简短的介绍,但您已经看到了使用 NestJS 和 Azure Cosmos DB 创建基本 CRUD API 的速度有多快。

如果您想进一步了解 NestJS 或 Azure,我推荐以下一些资源:


在Twitter上关注我,我很乐意讨论并接受您的建议!

文章来源:https://dev.to/azure/how-to-add-a-free-mongodb-database-to-your-nestjs-api-with-typeorm-2fop
PREV
如何使用 Kubernetes(以 YAML 文件为例)
NEXT
GraphQL 和 REST 与 Typescript、Prisma 和 Azure SQL:一见钟情!