NestJs 中使用 mongoose

news2025/1/18 18:49:04

在 NestJS 中链接 MongoDB 有两种方法。一种方法就是使用TypeORM来进行连接,另外一种方法就是使用Mongoose

此笔记主要是记录使用Mongoose的。所以我们先安装所需的依赖:

npm i @nestjs/mongoose mongoose

安装完成后,需要在AppModule中引入MongooseModule。具体实例如下:

import databaseConfig from "./config/database.config";
import { MongooseModule } from "@nestjs/mongoose";

@Module({
  imports: [
    MongooseModule.forRoot("mongodb://localhost:27017/managementsytem"),
    CommodityModule,
  ],
})
export class AppModule {}

MongooseModule.forRoot中的配置对象与mongoose.connect()的配置对象一致。

模型注入

在 Mongoose 中最为核心的是模式,因为每个模式都会转换成一个具体的MongoDB集合,并且模式定义了集合中文档的结构。

在 Mongoose 中模型主要是负责从底层创建数据和读取文档。

在 NestJs 中模式可以使用装饰器来创建,也可以使用 Mongoose 本身手动创建。使用装饰器创建的模式在代码量和代码可读性上都比 Mongoose 本身手动创建的要好。所以建议使用装饰器来创建模式。具体的实例如下:

import { HydratedDocument } from "mongoose";
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";

export type CommodityDocument = HydratedDocument<Commodity>;

@Schema()
export class Commodity {
  @Prop()
  name: string; // 商品名称

  @Prop()
  price: number; // 商品价格

  @Prop()
  stock: number; // 商品库存

  @Prop([String])
  tag: string; // 商品标签
}

export const CommoditySchema = SchemaFactory.createForClass(Commodity);

注意:
您还可以使用 DefinitionsFactory 类生成原始模式定义。这允许您手动修改根据您提供的元数据生成的架构定义。当模型的字段可能会进行扩展和删除的时候,我们就可以使用DefinitionsFactory来维护模式的数据。

@Schema()装饰器的作用主要是定义模式。在上述的例子中会把Commodity类映射到同名的MongoDB集合中,但是需要注意的是在 MongoDB 集合中的名称会添加一个’s’,即Commoditys。此装饰器可以接受一个可选参数,此参数主要是用于设置 MongoDB 的模型相关参数,具体的内容可以进入这里查看。

@Prop() 装饰器定义文档中的属性。例如,在上面的模式定义中,我们定义了三个属性:名称、价格和标签。借助 TypeScript 元数据(和反射)功能,可以自动推断这些属性的架构类型。但是,在无法隐式反映类型的更复杂场景(例如数组或嵌套对象结构)中,必须显式指示类型,如下所示:

@Prop([String])
tags: string[];

或者,@Prop() 装饰器接受选项对象参数(阅读有关可用选项的更多信息)。这样,您可以指示属性是否是必需的、指定默认值或将其标记为不可变。例如:

@Prop({ required: true })
name: string;

如果一个模型中带有另外一个属性的话,可以使用@Prop()装饰器。例如,Commodity 里面包含一个 supply 模型,这样我们需要在 Commodity 里面添加具体的类和引用。具体例子如下:

import * as mongoose from 'mongoose';
import { Supply } from '../owners/schemas/supply.schema';

@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Supply' })
supply: Supply;

如果有多个提供商,您的属性配置应如下所示:

@Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Supply' }] })
supply: Supply[];

最后,原始模式定义也可以传递给装饰器。例如,当属性表示未定义为类的嵌套对象时,这很有用。为此,请使用 @nestjs/mongoose 包中的 raw() 函数,如下所示:

@Prop(raw({
  firstName: { type: String },
  lastName: { type: String }
}))
details: Record<string, any>;

或者,如果您不想使用装饰器,则可以手动定义架构。例如:

export const CommoditySchema = new mongoose.Schema({
  name: String,
  price: Number,
  stock: Number,
  tags: [String],
});

当我们定义好模式后,就可以在对应的Module中进行定义,具体实例如下:

@Module({
  imports: [
    ConfigModule,
    MongooseModule.forFeature([
      { name: Commodity.name, schema: CommoditySchema },
    ]),
  ],
  controllers: [CommodityController],
  providers: [CommodityService],
})
export class CommodityModule {}

MongooseModule 提供了 forFeature() 方法来配置模块,包括定义应在当前范围内注册哪些模型。如果您还想在另一个模块中使用模型的话,你只要在 CommodityModule 中添加 MongooseModule 作为导出部分,并在另一个模块中导入 CommodityModule 就可以。

注册模式后,您可以使用 @InjectModel() 装饰器将 Commodity 模型注入到 CommodityService 中:

import { Injectable } from "@nestjs/common";
import { InjectModel } from "@nestjs/mongoose";
import { Model } from "mongoose";
import { Commodity } from "src/schemas/commodity.schemas";

@Injectable()
export class CommodityService {
  constructor(
    @InjectModel(Commodity.name) private commodityModel: Model<Commodity>
  ) {}

  create(commodity: Commodity) {
    const createdCommdity = new this.commodityModel(commodity);
    return createdCommdity.save();
  }
}

获取 Mongoose Connection 对象

有时您可能需要访问本机 Mongoose Connection 对象。例如,您可能希望对连接对象进行本机 API 调用。您可以使用 @InjectConnection() 装饰器注入 Mongoose Connection,如下所示:

import { Injectable } from "@nestjs/common";
import { InjectConnection } from "@nestjs/mongoose";
import { Connection } from "mongoose";

@Injectable()
export class CatsService {
  constructor(@InjectConnection() private connection: Connection) {}
}

多个数据库使用

有些项目需要多个数据库连接。这也可以通过该模块来实现。要使用多个连接,请首先创建连接。在这种情况下,连接命名就成为强制性的。具体实例如下:

import { Module } from "@nestjs/common";
import { CommodityModule } from "./module/commodity.module";
import { AccountModule } from "./module/account.module";
import { ConfigModule } from "@nestjs/config";
import configuration from "./config/configuration";
import databaseConfig from "./config/database.config";
import { MongooseModule } from "@nestjs/mongoose";

@Module({
  imports: [
    ConfigModule.forRoot({
      load: [configuration, databaseConfig],
      cache: true,
    }),
    MongooseModule.forRoot("mongodb://localhost:27017/managementsytem", {
      connectionName: "commodity",
    }),
    MongooseModule.forRoot("mongodb://localhost:27018/user", {
      connectionName: "user",
    }),
    CommodityModule,
    AccountModule,
  ],
})
export class AppModule {}

**注意:**您不应有多个没有名称或名称相同的连接,否则它们将被覆盖。

假如你设置了多个数据链接后,你必须在对应的模块中使用MongooseModule.forFeature()函数来声明链接哪个数据库。具体的例子如下:

@Module({
  imports: [
    ConfigModule,
    MongooseModule.forFeature(
      [{ name: Commodity.name, schema: CommoditySchema }],
      "commodity"
    ),
  ],
  controllers: [CommodityController],
  providers: [CommodityService],
  exports: [CommodityService],
})
export class CommodityModule {}

如果您在 AppModule 中声明了多个数据库,并在对应的模块中声明了链接的数据库名称,那么我们的提供者就需要在装饰器中添加第二个参数。具体实例如下:

@Injectable()
export class CatsService {
  constructor(
    @InjectModel(Commodity.name, "commodities")
    private commodityModel: Model<Commodity>
  ) {}

  async create(commodity: Commodity) {
    const createdCommdity = new this.commodityModel(commodity);
    const _res = await createdCommdity.save();
    return _res;
  }
}

您还可以为给定连接注入连接:

import { Injectable } from "@nestjs/common";
import { InjectConnection } from "@nestjs/mongoose";
import { Connection } from "mongoose";

@Injectable()
export class CommodityService {
  constructor(@InjectConnection("commodity") private connection: Connection) {}
}

要将给定连接注入到自定义提供程序(例如工厂提供程序),请使用 getConnectionToken() 函数,将连接名称作为参数传递。

{
  provide: CommodityService,
  useFactory: (commodityConnection: Connection) => {
    return new CommodityService(commodityConnection);
  },
  inject: [getConnectionToken('commodity')],
}

Mongo 钩子(中间件)

Mongo 钩子的使用场景一般都是在编写自己的插件时才会使用。

Mongo 的中间件是在模式级别指定的,对于编写插件很有用。编译模型后调用 pre() 或 post() 在 Mongoose 中不起作用。要在模型注册之前注册钩子,请使用 MongooseModule 的 forFeatureAsync() 方法以及工厂提供程序(即 useFactory)。通过这种技术,您可以访问模式对象,然后使用 pre() 或 post() 方法在该模式上注册挂钩。具体实例如下:

@Module({
  imports: [
    MongooseModule.forFeatureAsync([
      {
        name: Commodity.name,
        useFactory: () => {
          const schema = CommoditySchema;
          schema.pre("save", function () {
            console.log("Hello from pre save");
          });
          return schema;
        },
      },
    ]),
  ],
})
export class AppModule {}

与其他工厂提供者一样,我们的工厂函数可以是异步的,并且可以通过注入注入依赖项。

@Module({
  imports: [
    MongooseModule.forFeatureAsync([
      {
        name: Commodity.name,
        imports: [ConfigModule],
        useFactory: (configService: ConfigService) => {
          const schema = CommoditySchema;
          schema.pre('save', function() {
            console.log(
              `${configService.get('APP_NAME')}: Hello from pre save`,
            ),
          });
          return schema;
        },
        inject: [ConfigService],
      },
    ]),
  ],
})
export class AppModule {}

中间件中使用插件

要为给定架构注册插件,请使用 forFeatureAsync() 方法。

@Module({
  imports: [
    MongooseModule.forFeatureAsync([
      {
        name: Commodity.name,
        useFactory: () => {
          const schema = CommoditySchema;
          schema.plugin(require("mongoose-autopopulate"));
          return schema;
        },
      },
    ]),
  ],
})
export class AppModule {}

要一次为所有模式注册插件,请调用 Connection 对象的 .plugin() 方法。您应该在创建模型之前访问连接;为此,请使用连接工厂:

import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";

@Module({
  imports: [
    MongooseModule.forRoot("mongodb://localhost/test", {
      connectionFactory: (connection) => {
        connection.plugin(require("mongoose-autopopulate"));
        return connection;
      },
    }),
  ],
})
export class AppModule {}

鉴别器

鉴别器是一种模式继承机制。它们使您能够在同一底层 MongoDB 集合之上拥有具有重叠架构的多个模型。这个功能相当于模型形成继承关系。

假设您想在单个集合中跟踪不同类型的事件。每个事件都会有一个时间戳。

@Schema({ discriminatorKey: "kind" })
export class Event {
  @Prop({
    type: String,
    required: true,
    enum: [ClickedLinkEvent.name, SignUpEvent.name],
  })
  kind: string;

  @Prop({ type: Date, required: true })
  time: Date;
}

export const EventSchema = SchemaFactory.createForClass(Event);

SignedUpEvent 和 ClickedLinkEvent 实例将与通用事件存储在同一集合中。

现在,让我们定义 ClickedLinkEvent 类,如下所示:

@Schema()
export class ClickedLinkEvent {
  kind: string;
  time: Date;

  @Prop({ type: String, required: true })
  url: string;
}

export const ClickedLinkEventSchema =
  SchemaFactory.createForClass(ClickedLinkEvent);

和 SignUpEvent 类:

@Schema()
export class SignUpEvent {
  kind: string;
  time: Date;

  @Prop({ type: String, required: true })
  user: string;
}

export const SignUpEventSchema = SchemaFactory.createForClass(SignUpEvent);

完成此操作后,使用鉴别器选项为给定模式注册鉴别器。它适用于 MongooseModule.forFeature 和 MongooseModule.forFeatureAsync:

import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";

@Module({
  imports: [
    MongooseModule.forFeature([
      {
        name: Event.name,
        schema: EventSchema,
        discriminators: [
          { name: ClickedLinkEvent.name, schema: ClickedLinkEventSchema },
          { name: SignUpEvent.name, schema: SignUpEventSchema },
        ],
      },
    ]),
  ],
})
export class EventsModule {}

完成上述的代码后,其实模型之间的关系如下:

在这里插入图片描述

异步配置

当您需要异步而不是静态地传递模块选项时,请使用 forRootAsync() 方法。与大多数动态模块一样,Nest 提供了多种处理异步配置的技术。

一种技术是使用工厂函数:

MongooseModule.forRootAsync({
  useFactory: () => ({
    uri: "mongodb://localhost/nest",
  }),
});

与其他工厂提供者一样,我们的工厂函数可以是异步的,并且可以通过注入注入依赖项。

MongooseModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    uri: configService.get<string>("MONGODB_URI"),
  }),
  inject: [ConfigService],
});

或者,您可以使用类而不是工厂来配置 MongooseModule,如下所示:

MongooseModule.forRootAsync({
  useClass: MongooseConfigService,
});

上面的构造在 MongooseModule 中实例化 MongooseConfigService,使用它来创建所需的选项对象。请注意,在此示例中,MongooseConfigService 必须实现 MongooseOptionsFactory 接口,如下所示。MongooseModule 将在所提供的类的实例化对象上调用 createMongooseOptions() 方法。

@Injectable()
export class MongooseConfigService implements MongooseOptionsFactory {
  createMongooseOptions(): MongooseModuleOptions {
    return {
      uri: "mongodb://localhost/nest",
    };
  }
}

如果您想重用现有的选项提供程序而不是在 MongooseModule 内创建私有副本,请使用 useExisting 语法。

MongooseModule.forRootAsync({
  imports: [ConfigModule],
  useExisting: ConfigService,
});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/884585.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端基础学习笔记

目录 1.HTML部分1.HTML简介2.实例介绍3.标题4.段落5.链接6.图像7.表格8.速查列表1.基本文档2.基本标签3.文本格式化4.链接5.图片6.无序列表7.有序列表8.定义列表9.表格10.框架11.表单 2.CSS部分1.CSS简介1.什么是CSS2.为什么使用CSS3.CSS作用 2.基本用法1.CSS语法2.CSS应用方式…

Failed to resolve component: v-data-table“. vue3 + vuefity 使用 v-data-table 报错解决

在使用 vue3 vuetify 开发项目的过程中用到了 v-data-table 组件&#xff0c;结果在使用的过程中发现加载失败控制台报错。 [Vue warn]: Failed to resolve component: VDataTable解决方案&#xff1a; import { VDataTable } from vuetify/labs/VDataTable参考文档: https:…

双碳目标下基于“遥感+”多技术融合在碳储量、碳排放、碳循环、温室气体教程

详情点击链接&#xff1a;双碳目标下基于“遥感”多技术融合在碳储量、碳排放、碳循环、温室气体教程 一&#xff1a;双碳视角下遥感技术的研究方向 1.双碳背景及遥感的现实需求 2.全球碳库、碳收支及碳循环现状 3.碳储量、碳收支与碳循环中的遥感技术 4.ENVI及ArcGIS软件一体…

(三) CUDA 硬件实现

一组带有on-chip 共享内存的SIMD多处理器 GPU可以被看作一组多处理器, 每个多处理器使用单一指令&#xff0c;多数据架构(SIMD)【单指令流多数据流】 在任何给定的时钟周期内&#xff0c;多处理器的每个处理器执行同一指令&#xff0c;但操作不同的数据 每个多处理器使用以下…

广告ROI可洞察到订单转化率啦

toB广告营销人的一日三问&#xff1a; 如何实现线索增长&#xff1f;如何获取更多高质量线索&#xff1f;如何能用更少的钱拿到更多高质量的线索&#xff1f; < 广告营销的终极目标&#xff0c;就是提升ROI > 从ROI公式中&#xff0c;可以找到提升广告营销ROI的路径&…

Linux:shell脚本循环语句

目录 一、循环含义 二、echo命令 三、for 3.1.将1到100累加求和 3.2批量添加用户 3.3 根据IP地址检查主机状态 四、 while 和 until 4.1 猜价格 4.2 1-100求和 一、循环含义 循环含义 将某代码段重复运行多次&#xff0c;通常有进入循环的条件和退出循环的条件 重复…

kali搭建vulhub漏洞靶场

安装kali 下载kali作为虚拟环境&#xff0c; Get Kali | Kali Linux 通过vmvare打开&#xff0c;默认账号密码kali/kali 修改root密码 su passwd root 如果一些配置普通用户做不了就切换kali&#xff0c;或sudo 命令 kali配置 apt换源 echo > /etc/apt/sources.list v…

java对大文件分片上传

这里记录一下&#xff0c;Java对大文件的切分&#xff0c;和后端接口分片上传的实现逻辑 正常&#xff0c;前后端分离的项目其实是前端去切分文件&#xff0c;后端接口接收到切分后的分片文件去合并&#xff0c;这里都用java来记录一下。特别说明&#xff1a;我这里用的是zip包…

Codeforces Round 888 (Div. 3)ABC

Codeforces Round 888 (Div. 3) 目录 A. Escalator Conversations题目大意思路代码 B. Parity Sort题目大意思路代码 C. Tiles Comeback题目大意思路代码 A. Escalator Conversations 题目大意 判断有多少个人能够通过站在楼梯上的操作和VLAD一样高或者是VLAD通过站在楼梯上的…

6.1 MyBatis基础

1.MyBatis概述 MyBatis本是apache的一个开源项目iBatis&#xff0c;2010年这个项目由apache software foundation迁移到了google code&#xff0c;并且改名为MyBatis&#xff0c;2013年11月MyBatis又被迁移到Github。 MyBatis是一个支持普通SQL查询、存储过程以及高级映射的持…

欧拉算法与埃氏筛法比较

#include<iostream> using namespace std; bool data[100000005]; // zhishu用于存储质数的数组 &#xff0c;cnt下标 int zhishu[100000000],cnt0;int main() {data[1] 1;// 1表示素数 int n;cin >> n;// 循环遍历for(int i2;i<n;i){if(data[i] 0){// 表明是…

嵌入式学习之C语言指针部分复习

今天主要把C语言的指针部分再次认真的复习了一下&#xff0c;对于指针的整体框架有了更加深刻的理解&#xff0c;特别要重点区分函数指针&#xff0c;指针函数&#xff0c;数组指针&#xff0c;指针数组部分&#xff0c;对于这部分的应用回非常的重要&#xff0c;而且C语言指针…

海龟绘图——长方体(包含建立和销毁的过程)

运行结果&#xff1a; 代码&#xff1a; import turtle turtle.setup(1024,1024,200,200)# 移动画笔 turtle.penup() turtle.goto(-200,200) turtle.pendown() # 绘制第一条实线 turtle.setheading(0) turtle.forward(300) # 绘制第二条实线 turtle.setheading(18045) turtle.…

Python语法基础--条件选择

学习目标 使用比较运算符编写布尔表达式。使用random.randint(a,b)或者random.random()函数来生成随机数。编写布尔表达式(AdditionQuiz)。使用单向if语句实现选择控制。使用单向if语句编程。使用双向if-else语句实现选择控制。使用嵌套if和多向if-elif-else语句实现选择控制。…

winform使用IrisSkin4.dll换肤后排除控件

设置控件的tag属性为9999 参考&#xff1a; https://www.cnblogs.com/mq0036/p/6654219.html

YOLOV5改进:加入RCS-OSA模块,提升检测速度

1.该文章属于YOLOV5/YOLOV7/YOLOV8改进专栏,包含大量的改进方式,主要以2023年的最新文章和2022年的文章提出改进方式。 2.提供更加详细的改进方法,如将注意力机制添加到网络的不同位置,便于做实验,也可以当做论文的创新点。 2.涨点效果:RCS-OSA模块更加轻量化,有效提升检…

一场大火烧毁了印度的芯片梦 | 百能云芯

谈起印度的半导体发展史&#xff0c;鲜为人知的是&#xff0c;该国曾有可能成为全球半导体制造业的重要中心。然而&#xff0c;一个意外的事件彻底改变了历史进程&#xff0c;让印度错失了超越台积电的机会。 01半导体制造潜力 在高科技行业&#xff0c;也许很多人都不看好印度…

UE5.2程序发布及运行问题记录

发布后的程序默认是以全屏模式启动运行的&#xff0c;通过添加以下命令行参数&#xff0c;可实现程序的窗口模式运行&#xff1a; -ResX1280 -ResY720 -WINDOWED 发布后的程序&#xff0c;启动时&#xff0c;提示显卡驱动警告&#xff08;如图1所示&#xff09;&#xff0c;但是…

Java-Sec-Code靶场

文章目录 前言1.靶场搭建靶场地址、环境Window环境修改点 靶场通关和源码分析命令注入RCE反序列化fastjson反序列化目录穿越文件上传Spel表达式sql注入poi-ooxml组件XXE 总结 前言 一直都是一个Java盲&#xff0c;但是如今Java却占据了开发的半壁江山&#xff0c;平时遇见的多…