NestJS实战04-如何在NestJS使用Mongoose

by 雪隐 from juejin.cn/user/143341...

本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权

概要

在开始本章之前,希望大家先看下官方文档Mongo,此外我还将对接口进行保护,请阅读之前的博客文章 接口防护对前置知识进行了解。

MongoDB介绍以及优缺点

MongoDB是一种开源的、高性能、面向文档的NoSQL数据库,其名称"Mongo"来自于"humongous"(庞大的)一词,它旨在处理大规模的、非结构化或半结构化数据。MongoDB是目前最流行的NoSQL数据库之一,它具有一些独特的特点和优势,同时也有一些限制和缺点。

优点:

  1. 灵活的文档模型:MongoDB使用文档(BSON格式)来存储数据,文档可以包含不同类型的字段和嵌套文档,这种灵活性使其适用于多种数据结构和应用场景。
  2. 水平扩展:MongoDB支持水平扩展,可以轻松地将新节点添加到集群中以处理更多的负载,这使得它适用于大规模和高吞吐量的应用。
  3. 高性能:MongoDB的内部存储引擎具有出色的性能,特别适用于读取密集型和写入密集型应用。它还提供了自动负载均衡和分片来优化性能。
  4. 丰富的查询功能:MongoDB支持复杂的查询,包括范围查询、全文搜索和地理空间查询。它还具有强大的聚合框架,可以进行数据分析和转换。
  5. 自动故障转移:MongoDB具备自动故障转移功能,能够在主节点故障时自动选举新的主节点,保证了系统的高可用性。
  6. 社区和生态系统:MongoDB拥有庞大的社区支持和丰富的生态系统,有大量的第三方工具和驱动程序可供选择,使开发更加便捷。
  7. 容易学习和使用:MongoDB的查询语言和操作方式相对简单,开发人员可以很快上手。

缺点:

  1. 不支持事务:MongoDB在某些情况下不支持事务,虽然在较新的版本中引入了部分事务支持,但仍不适用于所有场景,这使得它在需要强一致性和事务支持的应用中有限制。
  2. 较大的存储空间需求:由于MongoDB的文档存储方式,它在某些情况下可能需要更多的存储空间,尤其是在包含大量小文档的集合中。
  3. 复杂的查询性能:复杂查询可能会导致性能下降,特别是在没有正确建立索引的情况下。
  4. 缺乏严格的模式:虽然文档模型的灵活性是MongoDB的优点之一,但也可能导致数据的不一致性,因为没有严格的模式来限制数据的结构。
  5. 不适合关系型数据:如果应用程序需要复杂的关系型数据操作(如多表连接),MongoDB可能不是最佳选择。

总的来说,MongoDB是一款强大的NoSQL数据库,适用于众多应用场景,特别是需要处理大规模和半结构化数据的应用。但开发人员需要根据具体需求和应用场景来权衡其优势和限制,以确定是否是最适合的数据库解决方案。在一些情况下,MongoDB也可以与关系型数据库结合使用,以充分利用其各自的优势。

Mongo的Docker环境准备

  1. 先根据官网安装docker和docker-compose环境

  2. docker-compose.yml编写

yml 复制代码
version: '3'
services:
  mongodb-container:
    image: mongo:6.0
    container_name: mongodb-container
    environment:
      TZ: Asia/Shanghai
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: 123456
    volumes:
      - "./data2/mongo_data:/data/db"
    ports:
      - 27017:27017

然后在当前文件夹路径下,执行下面的命令,运行容器。

shall 复制代码
docker-compose up -d
  1. 连接环境测试

我用的图形化界面是MongoDB Compass,大家可以根据自己的喜好来选择图形化界面。效果如下:

导入Mongoose

好了废话不多说了,直接导入Mongoose

  1. 安装依赖
css 复制代码
pnpm i @nestjs/mongoose mongoose
  1. app.module.ts
ts 复制代码
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    // ...
    MongooseModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => {
        const uri = `mongodb://${configService.get('MONGO_CONFIG').HOST}:27017`;
        return {
          uri,
          dbName: configService.get('MONGO_CONFIG').DB_NAME,
          auth: {
            username: configService.get('MONGO_CONFIG').USER,
            password: configService.get('MONGO_CONFIG').PASSWORD,
          },
        };
      },
      inject: [ConfigService],
    }),
    // ...
  ],
  controllers: [],
  providers: [
    // ...
  ],
})
export class AppModule {}
  • MongooseModule.forRootAsync:这是用于配置MongoDB连接的关键部分。

    • imports: [ConfigModule]:首先,从ConfigModule中导入配置服务,以便在后续配置中使用。
    • useFactory:这个选项定义了MongoDB连接的配置工厂函数。它接受configService参数,用于从配置文件中获取连接参数。
    • inject: [ConfigService]:这里将ConfigService注入到工厂函数中,以便在工厂函数中访问配置服务。
    • uri:这里通过 configService 获取了MongoDB连接URI,包括主机名、端口和认证信息。
    • dbName:从配置中获取了MongoDB的数据库名称。
    • auth:通过配置获取了MongoDB的认证信息,包括用户名和密码。
  1. 配置文件 .dev.yml

注意用户名和密码必须和docker-compose.yml里面的一致。

yml 复制代码
MONGO_CONFIG:
  HOST: 'localhost'
  USER: 'root'
  PASSWORD: '123456'
  DB_NAME: 'diary(你想要设定的数据库名字)'

配置文件位置

别忘了修改nest-cli.json(不多做解释了,看过我之前文章的应该都知道)

json 复制代码
{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true,
    // 加入下面这句
    "assets": [{ "include": "./config/*.yaml", "outDir": "./dist" }]
  }
}

schema或者entity

ts 复制代码
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type UserDocument = User & Document;

enum UserStatus {
  ACTIVE = 1,
  INACTIVE = 2,
  // ...其他状态
}

@Schema({
  // 使用timestamps自动生成createdAt和updatedAt字段
  timestamps: true,
  // toJSON要把密码字段隐藏
  toJSON: {
    transform: (_, ret) => {
      delete ret.__v;
      delete ret.password;
    },
  },
})
export class User {
  @Prop({ description: '用户名' })
  username: string;

  @Prop({ description: '加密的密码' })
  password: string;

  @Prop({ enum: UserStatus, description: '用户状态' })
  status: UserStatus;
}

export const UserSchema = SchemaFactory.createForClass(User);

在提供的代码示例中,使用了NestJS中的@nestjs/mongoose库来创建MongoDB的模式(Schema)以及mongoose库中的Document类。下面是对代码示例中的关键部分的解释:

  1. 导入依赖:

    • Prop:这是来自@nestjs/mongoose库的装饰器,用于将属性标记为MongoDB模式的字段。
    • Schema:同样来自@nestjs/mongoose库的装饰器,用于定义MongoDB模式的元数据。
    • SchemaFactory:用于创建MongoDB模式的工厂类。
    • Document:来自mongoose库,表示MongoDB文档的基本类。
  2. UserDocument类型:

    • UserDocument是一个类型别名,用于表示MongoDB中用户文档的类型。它是User类和Document类的交集,表示用户文档的结构。
  3. @Schema装饰器:

    • @Schema装饰器用于将元数据附加到User类,以定义MongoDB模式的行为。

    • timestamps: true:这个选项会自动在文档中创建createdAtupdatedAt字段,用于记录文档的创建和更新时间。

    • toJSON:这个选项定义了在将文档转换为JSON格式时要执行的操作。

      • transform函数:在这里,它删除了__v字段和password字段,以确保密码不会在JSON表示中暴露。
  4. User类:

    • User类是用于定义MongoDB文档模式的类。它包含了文档的各个字段和它们的描述。

    • @Prop装饰器用于定义每个字段的属性。

      • 例如,@Prop({ description: '用户名' })定义了username字段,并提供了一个描述。
  5. UserSchema:

    • UserSchema是通过SchemaFactory.createForClass(User)创建的MongoDB模式,它将User类转化为MongoDB的模式对象。这个模式可以用于在MongoDB数据库中创建和操作文档。

总结:上述代码示例演示了如何使用NestJS和@nestjs/mongoose库来定义MongoDB文档的模式,包括字段的定义、枚举类型的使用、自动生成时间戳、字段的隐藏等操作。这种方式使得开发人员可以轻松地与MongoDB数据库交互,并在NestJS应用程序中使用这些模式来操作数据库文档。

mongo语法介绍

网上找了搜索下Mongoose基础入门相关的内容,这方面的资料有很多。

如果你不想细看,以下是一些MongoDB查询语法的简单示例,以及在代码中如何使用它们:

  1. 查询所有用户

    ts 复制代码
    const allUsers = await this.userModel.find().lean();
  2. 根据条件查询用户

    • 查询具有特定用户名的用户:

      ts 复制代码
      const user = await this.userModel.findOne({ username }).lean();
    • 查询具有特定属性值的用户(例如,查询所有年龄大于30的用户):

      ts 复制代码
      const users = await this.userModel.find({ age: { $gt: 30 } }).lean();
    • 使用逻辑操作符进行复杂查询(例如,查询用户名为 "John" 且年龄大于25的用户):

      ts 复制代码
      const users = await this.userModel.find({ username: "John", age: { $gt: 25 } }).lean();
  3. 根据ID查询用户

    ts 复制代码
    const user = await this.userModel.findById(id).lean();
  4. 对查询结果进行排序

    • 按照特定字段升序排序(例如,按照用户名升序排序):

      ts 复制代码
      const users = await this.userModel.find().sort({ username: 1 }).lean();
    • 按照特定字段降序排序(例如,按照年龄降序排序):

      ts 复制代码
      const users = await this.userModel.find().sort({ age: -1 }).lean();
  5. 限制查询结果数量

    • 查询前n个用户(例如,查询前5个用户):

      ts 复制代码
      const users = await this.userModel.find().limit(5).lean();
  6. 跳过查询结果的前几项

    • 跳过前n个用户,返回剩余的结果(例如,跳过前10个用户,返回从第11个用户开始的结果):

      ts 复制代码
      const users = await this.userModel.find().skip(10).lean();

这些只是MongoDB查询语法的一些基本示例。你可以根据具体的业务需求和条件来构建更复杂的查询。在上述代码中,this.userModel 是Mongoose的模型实例,你可以使用模型的方法来执行这些查询。lean() 方法用于将查询结果转换为普通JavaScript对象,而不是Mongoose文档对象。

user模块以及Auth模块做成

  • user.controller.ts
ts 复制代码
import { Controller, Post, Body } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { SwaggerDocumentation } from '../decorator/swagger.decorator';
// import { Public } from '../guards';

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

  @Post('create')
  // 这个接口我不想暴露出去,这里偷个懒,想要调用这个接口且不想带上token的情况下,把这个注释打开
  // @Public()
  @SwaggerDocumentation('创建用户', '返回创建结果', 'bad request例子', String)
  create(@Body() createUserDto: CreateUserDto) {
    return this.userService.create(createUserDto);
  }
}
  • user.module.tss
ts 复制代码
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { MongooseModule } from '@nestjs/mongoose';
import { User, UserSchema } from './entities/user.entity';

@Module({
  // 导入Mongoose
  imports: [
    MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
  ],
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService],
})
export class UserModule {}
  • user.service.ts
ts 复制代码
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { BusinessException } from '../exceptions/business.exception';
import { InjectModel } from '@nestjs/mongoose';
import { User } from './entities/user.entity';
import { Model } from 'mongoose';
import * as bcrypt from 'bcryptjs';

@Injectable()
export class UserService {
  constructor(@InjectModel(User.name) private userModel: Model<User>) {}

  /**
   * 创建用户
   * @param createUserDto
   * @returns
   */
  async create(createUserDto: CreateUserDto) {
    let createdUser;

    const encryptedPassword = this.encodePassword(createUserDto.password);

    const userToCreate = {
      ...createUserDto,
      password: encryptedPassword,
    };

    createdUser = await this.userModel.create(userToCreate);

    if (!createdUser) {
      throw new BusinessException('用户创建失败');
    }

    return createdUser;
  }

  // 对用户密码进行编码
  public encodePassword(password: string): string {
    const salt = bcrypt.genSaltSync(10);
    return bcrypt.hashSync(password, salt);
  }

  findOne(username: string): Promise<User | null> {
    return this.userModel.findOne({ username }).lean();
  }

  findById(id: string): Promise<User | null> {
    return this.userModel.findById(id).lean();
  }
}

提供的代码示例是用于处理用户相关操作的UserService服务。以下是与MongoDB语法相关的部分:

  1. 创建用户(create方法) :

    • create方法中,通过this.userModel.create(userToCreate)使用MongoDB模型来创建一个新用户文档。userToCreate是一个包含用户信息的对象。
  2. 对用户密码进行编码(encodePassword方法) :

    • encodePassword方法使用了bcrypt库来对用户密码进行哈希加密,以确保密码的安全性。这不仅是一种安全最佳实践,还是与MongoDB中存储敏感数据相关的操作。
  3. findOne方法:

    • findOne方法使用了MongoDB的查询语法,通过this.userModel.findOne({ username })来查找具有指定用户名的用户文档。这是一个基本的MongoDB查询操作,通过提供查询条件来查找文档。
  4. findById方法:

    • findById方法类似于findOne,但是它使用文档的唯一标识符(通常是文档的ID)来查找文档。它使用this.userModel.findById(id)来执行查询。

总结:虽然上述代码示例的主要重点是NestJS框架的用法,但它还包含了与MongoDB集成相关的操作,包括创建、查询用户文档以及对用户密码进行哈希加密。这些MongoDB操作是基本的CRUD(创建、读取、更新、删除)操作,用于在应用程序中管理用户数据。

一些补充

  • 关于加密我这里用的是bcryptjs库而不是bcrypt,纯js的库对性能会有损耗,bcrypt与环境有关联,所以必须通过pnpm install或者npm install来安装。我发布的时候希望把自己本地依赖压缩一下直接放上去,所以就直接bcryptjs了,因为和环境的关联性不强,并且我的项目并没有性能上的要求。

  • auth模块和guard,麻烦大家看下接口防护这篇文章,里面内容基本差不多。重复内容我就不写了。

  • 最后用Postman来测试下接口,user如果没有用户的话,大家可以把@Public()这个注释打开,然后调用接口加一个用户进去,我这里现在还没创建用户,所以验证不通过

加一个用户进去以后

总结

这一章向大家简单介绍了下mongo的接入方法,mongo的一些基础语法。随着后面的文章继续深入,还会介绍更多的语法。接着,介绍了user模块和Auth模块来完成接口防护的工作。谢谢大家的支持,如果这篇文章对您有启发,请多多点赞加评论🙏!

本章代码

代码

相关推荐
小远yyds8 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
阿伟来咯~1 小时前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端1 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱1 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai1 小时前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨1 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
bysking2 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4112 小时前
无网络安装ionic和运行
前端·npm
理想不理想v3 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试