NestJS入门(2)——数据库、用户、备忘录模块初始化

项目搭建完成后根据我们的需求目前可以将功能模块大致分为用户、备忘录、配置、数据库模块(使用MongoDB),本章我们就来学习初始化这四个模块。

技术点

模块

在Nest中使用 @Module() 装饰器注解的类我们称之为模块,一般一组密切相关的功能被封装为一个模块,方便管理应用。

可以使用 CLI 命令快捷创建模块,$ nest g module moduleName

在本需求中用户相关功能、备忘录相关功能、数据库链接我们就可以将其封装为不同的模块。

装饰器

上面模块的解释中我们提到了装饰器,这个在前端领域中还属于一种较新的概念。

ES2016中装饰器是一个返回函数的表达式。使用时需要添加 @ 作为前缀,它可以接收目标对象、名称和属性描述符作为参数,并返回经过扩展功能后的同名函数。

简单示例,有一个基本 User 类,我们可以通过一个装饰器来实现让它的某个属性只读:

js 复制代码
function readonly (target, key, descriptor){
    descriptor.writable = false
    return descriptor
}

class User (){
    constructor(name){
        this.name = name
    }
    @readly
    hello(){
        return `${this.name} say Hello!`
    }
}

也可以通过一个装饰器实现对类的扩充:

js 复制代码
function userAge (age){
    return function(target){
        target.age = age
    }
}

@userAge(18)
class User (){
    constructor(name){
        this.name = name
    }
}

Nest中我们如果自定义装饰器的需要访问上下文、支持依赖注入、类型安全与框架集成的话需要使用 createParamDecorator 方法进行包裹,比如我们定义一个装饰器从请求中获取用户信息

js 复制代码
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);

// 使用
@Get() 
async findOne(@User() user: UserEntity) { 
    console.log(user); 
}

@Module() 装饰器

@Module() 装饰器接收的参数是一个对象:

属性 描述
providers 将由 Nest 注入器实例化,且至少可在本模块内共享的提供者
controllers 本模块中定义的需要实例化的控制器集合
imports 导入模块的列表,这些模块导出了本模块所需的提供者
exports 本模块提供的 providers 子集,这些提供者应可供导入本模块的其他模块使用。可以使用提供者本身或其令牌(provide 值)

共享模块

在 Nest 中模块默认都是单例,在整个应用程序生命周期中每个模块只会被实例化一次,这样做可以的好处:

  • 资源高效性。避免重复创建实例,减少内存开销。
  • 状态一致性
  • 依赖安全。避免出现多个实例导致的依赖关系错乱。

通过在模块的 exports 数组中导出实例,模块一旦创建就可以自动成为共享模块,可以在多个模块中引入共享该模块的实例。

全局模块

如果需要在多个地方导入相同的模块集,重复 imports 就会显得繁琐,这时可以通过使用 @Global() 装饰器将模块设置为全局模块。

js 复制代码
import { Module, Global } from '@nestjs/common'; 
import { UserController } from './user.controller'; 
import { UserService } from './user.service'; 

@Global() 
@Module({ 
    controllers: [UserController], 
    providers: [UserService], 
    exports: [UserService], 
}) 
export class UserModule {}

@Global() 装饰器使模块具有全局作用域,全局模块由根模块或核心模块仅注册一次,需要注入该服务的模块也无需在 imports 数组中重复导入。

动态模块

动态模块允许创建可在运行时配置的模块,在需要灵活、根据配置返回定制化模块时特别有用(如数据库不同环境配置)。

动态模块需要提供一个静态方法(通常叫做 register 或者 forRoot)来返回一个符合 DynamicModule 接口的对象,使用时在 @Module() 装饰器的 imports 属性中调用。

  • register 通常表示该动态模块是在调用时使用,不同的调用配置可能不同。
  • forRoot 通常表示希望一次性配置动态模块,并在多个地方复用该配置。

代码实现

配置模块初始化

为了区分开发、生产环境,我们需要通过不同的配置文件来管理不同的环境配置。

在 Node.js 的应用程序中通常使用 .env 文件设置不同环境的配置。

一、创建环境配置文件

在项目根目录创建:

  • .env.development,对应开发环境配置
  • .env.production,对应生产环境配置

.env.development 文件内容:

bash 复制代码
# 项目端口
PORT=9527

.env.production 文件内容:

bash 复制代码
# 项目端口
PORT=9528

二、引入配置模块

Nest 提供了一个 @nestjs/config 包,可以通过它创建一个 ConfigModule,并暴露一个加载相应 .env 文件的 ConfigService

ConfigModule 就是之前提过的动态模块,允许我们通过传递参数定制其内部逻辑。

接下来我们通过该包去创建配置模块。

安装依赖:

bash 复制代码
$ npm i --save @nestjs/config

配置模块一般会在根模块 AppModule 引入,并使用静态方法 forRoot 来控制其行为,这个步骤中环境变量的键值对会被解析和处理。

js 复制代码
import { Module } from '@nestjs/common'; 
import { ConfigModule } from '@nestjs/config'; 

@Module({ 
imports: [ConfigModule.forRoot({
    envFilePath: `.env.${process.env.NODE_ENV || 'development'}`
})], 
}) 

export class AppModule {}

上述配置会从项目根目录加载并解析对应环境的 .env 文件,将其中的键值对与分配给 process.env 的环境变量合并,并将结果存储在一个可通过 ConfigService 访问的私有结构中。

forRoot 方法会注册 ConfigService 提供者,该提供者提供了用于读取配置变量的 get 方法。

当某个变量同时存在于运行时环境变量和 .env 文件中时,运行时环境变量具有优先权。

配置配置一般会在很多其他模块中使用,可以通过将 forRoot 参数的 isGlobal 属性设置为 true,将其声明为全局模块。

js 复制代码
ConfigModule.forRoot({
    envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
    isGlobal: true
})

当其他模块使用时就无需再次通过 @Module 导入,只需从 @nestjs/config 引入 ConfigService 即可使用相关数据及功能(见后续数据库模块初始化中示例)。

三、调整 main.ts 使用配置文件变量

目前配置存储在服务中,main.ts 如果要访问配置,必须通过 app.get 方法获取服务使用:

ts 复制代码
import { NestFactory } from '@nestjs/core';
import { ConfigService } from '@nestjs/config';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);
  const port = configService.get('PORT');
  await app.listen(port ?? 3000);
}
bootstrap();

四、配置不同环境启动脚本

package.json 中配置不同环境的启动命令:

json 复制代码
{
  "scripts": {
    "start": "NODE_ENV=production nest start",
    "start:dev": "NODE_ENV=development nest start --watch",
  }
}

数据库模块初始化

MongoDB 常用的连接器是 Mongoose,Nest 提供了针对它的专用包 @nestjs/mongoose

一、下载依赖

首先安装依赖:

bash 复制代码
$ npm i @nestjs/mongoose mongoose

二、环境变量增加数据库配置

设置不同环境的数据库配置,以开发环境举例:

bash 复制代码
# 项目端口
PORT=9527

# MongoDB 配置
DB_HOST=localhost # 数据库地址
DB_PORT=27017 # 数据库端口
DB_NAME=doraemonNotebook # 数据库名称
DB_USER=developer # 数据库账号
DB_PASS=developerPass # 数据库密码

三、引入数据库模块初始化全局连接

导入 MongooseModule 到根模块:

js 复制代码
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
    }),
    MongooseModule.forRootAsync({
      useFactory: (configService: ConfigService) => {
        return {
          uri: `mongodb://${configService.get('DB_USER')}:${configService.get('DB_PASS')}@${configService.get('DB_HOST')}:${configService.get('DB_PORT')}/${configService.get('DB_NAME')}`,
        };
      },
      inject: [ConfigService],
    }),
  ]
})

export class AppModule {}

MongooseModule 也为动态模块。

因为数据库的配置参数需要在 ConfigModule 中实例化 .env文件后才能配置到环境变量中,所以需要使用暴露出来的 forRootAsnyc 异步方法来引入数据库模块,其提供了全局的连接配置。

后续各个模块使用只需要通过 MongooseModule.forFeature 来关注自己的数据模型定义和使用。

用户模块初始化

一、创建模块

创建用户模块:

bash 复制代码
$ nest g module user

Nest 会自动在项目 src 目录下创建 user 文件夹及文件夹内的 user.module.ts 文件,同时并自动在 AppModule 中引入。

二、定义模型

根据领域就近原则我们在 user 目录下创建一个 schemas 目录及其下面的 user.schema.ts 文件,并定义以下内容:

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

export type UserDocument = HydratedDocument<User>;

@Schema({ timestamps: true }) // 自动添加 createdAt 和 updatedAt 字段
export class User {
  @Prop({ required: true })
  name: string;

  @Prop({ required: true, unique: true }) // 定义必填及唯一索引
  phone: number;

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

export const UserSchema = SchemaFactory.createForClass(User);

@Schema() 装饰器将类标记为模式定义。它将 User 类映射到同名的 MongoDB 集合,但会在末尾添加一个"s",Mongo 集合的名称将是 Users。它接受一个可选参数,同 mongoose.Schema 类构造函数的第二个参数,详情参考

@Prop() 装饰器定义文档属性。它也可以接受一个可选参数,同 mongoose.SchemaTypes 配置,详情参考

三、注册模型

更新 UserModule 模块,注册 User 模型:

js 复制代码
// user.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { UserSchema, User } from './schemas/user.schema';

@Module({
  imports: [
    MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
  ],
})
export class UserModule {}

forFeature 配置数据库模块,告诉 Nest 定义名为 User 的模型,该模型基于 UserSchema 定义。

注意:这里 User.name 指的是 User 类的名称 'User',是类的静态属性,而不是使用 @Prop() 装饰的 'name' 实例属性。

备忘录模块初始化

一、创建模块

创建用户模块:

bash 复制代码
$ nest g module todo

二、定义模型

todo 目录下创建一个 schemas 目录及其下面的 todo.schema.ts 文件,并定义以下内容:

js 复制代码
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
import type { ObjectId } from 'mongoose';

export type TodoDocument = HydratedDocument<Todo>;

@Schema({ timestamps: true })
export class Todo {
  @Prop({ required: true })
  title: string;

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

  @Prop({ required: true, enum: ['pending', 'completed'], default: 'pending' })
  status: string;

  @Prop({ required: true })
  userId: ObjectId;
}

export const TodoSchema = SchemaFactory.createForClass(Todo);

三、注册模型

更新 TodoModule 模块,注册 Todo 模型:

js 复制代码
// todo.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { TodoSchema, Todo } from './schemas/todo.schema';

@Module({
  imports: [
    MongooseModule.forFeature([{ name: Todo.name, schema: TodoSchema }]),
  ],
})
export class TodoModule {}
相关推荐
麻辣小蜗牛5 小时前
以 NestJS 为原型看懂 Node.js 框架设计:Provider Scope
nestjs
你的电影很有趣8 小时前
lesson72:Node.js 安全实战:Crypto-Js 4.2.0 与 Express 加密体系构建指南
javascript·安全·node.js
玩代码8 小时前
使用 nvm(Node Version Manager) 高效管理Node.js
node.js·vue·nvm
api_1800790546011 小时前
异步数据采集实践:用 Python/Node.js 构建高并发淘宝商品 API 调用引擎
大数据·开发语言·数据库·数据挖掘·node.js
_孤傲_12 小时前
webpack实现常用plugin
前端·webpack·node.js
小菜摸鱼1 天前
Node.js + vue3 大文件-切片上传全流程(视频文件)
前端·node.js
PaytonD1 天前
LoopBack 2 如何设置静态资源缓存时间
前端·javascript·node.js
许久'1 天前
环境搭建node.js gnvm
node.js
细节控菜鸡2 天前
Webpack 核心知识点详解:proxy、热更新、Loader与Plugin全解析
前端·webpack·node.js