知道了,去卷后端 →「Nest.js 入门及实践」:)

news2024/12/24 8:54:39

为什么学习 Nest ?

  • 前端已 🙈,去卷后端 🐒 → 广度和深度的问题,不可代替性
  • 仅前端开发的应用没有记忆,不能互联 🤔 → 学习后端,为应用赋能
  • 同为 JS 语言,学习成本较小,多门技能多条路 😆 → 了解服务端编码思想
  • 同类型框架中占绝对领先地位(star、下载量、Google 趋势)→ Nest.js 优势

🚩 Nest.js 介绍、HelloWorld、CRUD、Swagger、数据库集成 → ~~原理?~ 🤷 应用!🙋

Nest.js 介绍

Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用 JavaScript 的渐进增强的能力,使用并完全支持 TypeScript (仍然允许开发者使用纯 JavaScript 进行开发),并结合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数响应式编程)。

在底层,Nest 构建在强大的 HTTP 服务器框架上,例如 Express (默认),并且还可以通过配置从而使用 Fastify

可扩展?

  • 模块化架构:应用拆分成更小的模块,各部分可独立地进行扩展和修改,不会对整个系统造成大的影响

    // 模块化架构 → 类比前端组件化、页面?
    
    src
    ├── ...
    ├── app.module.ts
    ├── modules
    ├────── car/
    ├────── engine/
  • 依赖注入:内置依赖注入容器,用于管理组件之间的依赖关系,将组件的依赖关系解耦。依赖注入的核心概念是将组件的依赖项从组件内部获取,而不是在组件内部直接创建或实例化依赖项。通过这种方式,组件不需要关心依赖项的创建和生命周期管理,而是通过依赖注入容器(DI 容器)来自动解析和提供所需的依赖项。
    @Injectable()
    class CarService {
      private engine: Engine;
    
      constructor(engine: Engine) {
        this.engine = engine;
      }
      // 使用 this.engine 进行操作
    }
  • 中间件和拦截器:Nest.js 提供了中间件和拦截器的机制,使得在请求的处理过程中可以插入自定义的逻辑。中间件和拦截器可以用于实现诸如权限验证、日志记录、缓存等功能。通过这些机制,你可以在应用程序的不同层面上进行扩展,而不必改变核心的业务逻辑。

    // 可以在不同层面插入自定义逻辑 → 可以类比前端的路由守卫?
     
    import { Injectable, NestMiddleware } from '@nestjs/common';
    import { Request, Response, NextFunction } from 'express';
    
    @Injectable()
    export class AuthMiddleware implements NestMiddleware {
      use(req: Request, res: Response, next: NextFunction) {
        // 在这里编写身份验证逻辑
        if (!req.headers.authorization) {
          // 如果请求头中没有授权信息,则返回未经授权的响应
          res.status(401).json({ message: 'Unauthorized' });
          return;
        }
    
        // 如果身份验证通过,则继续请求处理
        next();
      }
    }

  • 数据库集成:Nest.js 与多种数据库集成框架(如TypeORM、Mongoose等)兼容,这使得在应用程序中使用数据库变得更加容易,并且可以方便地进行扩展和迁移。

构建在 Express 之上?

Express.js 是一个基于 Node.js 的 Web 应用程序框架,可以被视为对 Node.js 的封装。Express.js 提供了简化和抽象化的 API,使得在 Node.js 上构建 Web 应用程序变得更加简单和高效

Node.js 本身提供了处理网络请求和构建 Web 服务器的能力,但它的 API 相对底层,需要开发者手动处理很多细节。而 Express.js 通过封装 Node.js 的功能,提供了更高层次的抽象和开发工具,使得构建 Web 应用程序变得更加便捷。

import * as http from "http";
const app = http.createServer();

app.on("request", (request, response) => {
  const { method, url } = request;
  if (method === 'GET' && url === '/hello') {
    response.end('hello world');
  }
});
app.listen(3000);

// Express 实现上述功能
const express = require('express');
const app = express();

app.get('/hello', (request, response) => {
  response.send('hello world');
});
app.listen(3000);

其实严格来说 Express.js 并不是一个框架,它只是提供了基于中间件的请求响应处理流程。Express.js 并没有规定代码应该怎么组织,怎么复用,怎么集成各种方案,所以代码能写成各种样子,对于大项目开发来说很难维护。所以出现了更上层的 Node 框架,比如 Egg、Midway、Nest 这些,它们额外提供了架构能力,这类框架也叫企业级开发框架

Nest.js 基于 Express.js 提供了更高层次的抽象和功能扩展。它引入了一些新的概念,如模块、控制器和中间件,以帮助开发者更好地组织和管理代码。Nest.js 还提供了一套强大的依赖注入系统,可以帮助解耦组件之间的依赖关系,并方便进行单元测试。

Nest.js 优势?

  • TypeScript 支持 → 题外话:一定是优势吗?

  • 无缝集成:直接使用 npm 安装常用的库

  • 模块化架构

  • 依赖注入

  • 强大的路由系统

  • 内置的中间件支持

  • 生态系统和插件...

Hello world

脚手架安装 & 项目初始化

$ npm i -g @nestjs/cli

$ nest -v // 9.5.0

$ nest new project-name

// "@nestjs/common": "^9.0.0",
// "@nestjs/core": "^9.0.0",
// "@nestjs/platform-express": "^9.0.0",

目录结构 & 文件介绍

src
├── app.controller.spec.ts // 针对控制器的单元测试
├── app.controller.ts // 带有单个路由的基本控制器
├── app.module.ts // 应用程序的根模块(root module)
├── app.service.ts // 具有单一方法的基本服务(service)
├── app.service.spec.ts // 针对服务的单元测试
├── main.ts // 应用程序的入口文件
  • 入口文件(main.js):应用程序的入口文件,它使用核心函数 NestFactory 来创建 Nest 应用程序的实例

    // src/main.ts
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      await app.listen(3000); // curl http://localhost:3000
    }
    bootstrap();
  • 模块(Modules):Nest.js 应用程序由多个模块组成,每个模块都是一个逻辑上的独立单元。模块可以包含路由、控制器、服务和其他相关的组件。模块之间可以进行嵌套和引用,形成层次化的结构。

    // src/app.module.ts
    import { Module } from '@nestjs/common';
    import { AppController } from './app.controller';
    import { AppService } from './app.service';
    
    @Module({
      imports: [], // 导入模块的列表,如果需要使用其他模块的服务,需要通过这里导入
      controllers: [AppController], // 控制器
      providers: [AppService],      // 服务提供者,处理具体的业务逻辑
    })
    export class AppModule {}
  • 控制器(Controllers):控制器处理来自客户端的请求,并将请求分发给相应的服务进行处理。控制器负责定义路由和请求处理程序,它们使用装饰器来标记路由和请求方法。

    // src/app.controller.ts
    import { Controller, Get } from '@nestjs/common';
    import { AppService } from './app.service';
    
    @Controller()
    export class AppController {
      constructor(private readonly appService: AppService) {}
    
      @Get()
      getHello(): string {
        return this.appService.getHello();
      }
    }

🚩 在 Nest.js 中,使用控制器(Controller)和服务(Service)的组合是一种推荐的架构模式。控制器负责处理客户端的请求和响应,而服务则负责处理具体的业务逻辑。这种模式的好处是将请求处理和业务逻辑分离,使代码更具可读性、可维护性和可测试性。

相关装饰器介绍

  • 路由装饰器(Route decorators):

    • @Get():定义处理 GET 请求的路由。

    • @Post():定义处理 POST 请求的路由。

    • @Put():定义处理 PUT 请求的路由。

    • @Delete():定义处理 DELETE 请求的路由。

    • @Patch():定义处理 PATCH 请求的路由。

    • ...
  • 请求体装饰器(Request body decorators):

    • @Body():从请求体中提取数据。

    • @Query():从查询参数中提取数据。

    • @Param():从路由参数中提取数据。

    • @Headers():从请求头中提取数据。

  • 响应装饰器(Response decorators):

    • @Res():注入原生 response 对象。

    • @Headers():设置响应头。

  • 状态码装饰器(Status code decorators):

    • @HttpCode():设置响应的状态码。

CRUD 接口实现

准备 user 模块

// $ nest generate|g module|mo user
// $ nest generate|g controller|co user
// $ nest generate|g service|s user

// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './modules/userBase/user.module';

@Module({
  imports: [UserModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

// src/modules/user/userBase.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';

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

user.controller.ts

import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
import { UserBaseService } from './userBase.service';
import { UserItem } from 'src/core/types/user';

@Controller('/userBase')
export class UserBaseController {
  constructor(private readonly userBaseService: UserBaseService) {}

  @Get('/getUserList')
  getUserList(): UserItem[] {
    return this.userBaseService.getUserList();
  }

  @Post('addUser')
  addUser(@Body() userData): UserItem[] {
    return this.userBaseService.addUser(userData);
  }

  @Get('getUserDetail/:id')
  getUserDetail(@Param('id') id: string): UserItem {
    return this.userBaseService.getUserDetail(id);
  }

  @Put('updateUser')
  updateUser(@Body() userData): UserItem[] {
    return this.userBaseService.updateUser(userData);
  }

  @Delete('deleteUser')
  deleteUser(@Query() query): UserItem[] {
    return this.userBaseService.deleteUser(query.id);
  }
}

user.service.ts

import { Injectable } from '@nestjs/common';
import { UserItem } from 'src/core/types/user';

@Injectable()
export class UserBaseService {
  private userList = []; // 没有数据库,暂时通过该方式定义数据

  getUserList(): UserItem[] {
    return this.userList;
  }

  addUser(userData: UserItem): UserItem[] {
    this.userList.push(userData);
    return this.userList;
  }

  getUserDetail(targetUserId: string): UserItem {
    const targetUserArray = this.userList.filter(
      (item) => item.id === parseInt(targetUserId),
    );
    return targetUserArray[0] || {};
  }

  updateUser(userData: UserItem): UserItem[] {
    this.userList = this.userList.map((item) => {
      if (item.id === userData.id) {
        return userData;
      }
      return item;
    });
    return this.userList;
  }

  deleteUser(deleteId): UserItem[] {
    this.userList = this.userList.filter(
      (item) => item.id !== parseInt(deleteId),
    );
    return this.userList;
  }
}

参数校验

在 Nest.js 中,可以使用类验证器(class-validator)库来进行参数校验

$ npm install class-validator class-transformer

// class-validator 常用的装饰器:
// @IsNumber():验证值是否为数字类型
// @IsString():验证值是否为字符串类型
// @IsBoolean():验证值是否为布尔类型
// @IsNotEmpty():验证值是否非空
// @IsOptional():验证值是否可选
// @IsEmail():验证值是否为有效的电子邮件地址
// @IsUrl():验证值是否为有效的 URL 地址
// @IsDate():验证值是否为有效的日期
// @Min():验证值是否大于或等于指定的最小值
// @Max():验证值是否小于或等于指定的最大值
// @Length():验证字符串长度是否在指定的范围内
// @Matches():验证字符串是否匹配指定的正则表达式
// @IsIn():验证值是否属于指定的允许值列表
// @IsNotEmptyObject():验证对象是否为非空对象

// 装饰器支持可选参数,例如:@IsNumber({message: 'id is not number!'})
// 具体参数说明:https://github.com/typestack/class-validator#readme

Body、Query 参数校验

  1. 创建 DTO(数据传输对象)类,用于定义要验证的数据结构和规则。DTO不是中间件或过滤器,而是一种设计模式,用于处理和验证输入数据。通过使用DTO进行参数验证,我们可以在控制器中对请求的数据进行验证,确保数据的完整性和有效性,从而减少错误和异常情况。

    // src/modules/user/dto/addUser.dto.ts
    import { IsNotEmpty, IsString, IsNumber, IsIn } from 'class-validator';
    
    export class AddUserDto {
      @IsNotEmpty({ message: 'id should not be empty' })
      @IsNumber({ allowNaN: false }, { message: 'id must be a number' })
      id: number;
    
      @IsNotEmpty()
      @IsString()
      name: string;
    
      @IsNotEmpty()
      @IsNumber()
      age: number;
    
      @IsNotEmpty()
      @IsIn([1, 2])
      gender: string;
    }
  2. 在控制器中使用 DTO 类进行参数校验

    // src/modules/user/user.controller.ts
    import { Controller, Post, Body } from '@nestjs/common';
    import { AddUserDto } from './dto/addUser.dto';
    import { DeleteUserDto } from './dto/deleteUser.dto';
    
    @Controller('users')
    export class UserController {
      @Post('addUser')
      addUser(@Body() userData: AddUserDto): UserItem[] {
        return this.userService.addUser(userData);
      }
      @Delete('deleteUser')
      deleteUser(@Query() query: DeleteUserDto): UserItem[] {
        return this.userService.deleteUser(query.id);
      }
    }
  3. 在应用程序的主模块中启用参数校验:

    // src/main.ts
    import { NestFactory } from '@nestjs/core';
    import { ValidationPipe } from '@nestjs/common';
    import { AppModule } from './app.module';
    
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
    
      // 全局使用管道,用于 Controller 层参数校验
      app.useGlobalPipes(new ValidationPipe());
    
      await app.listen(3000);
    }
    bootstrap();

Params 参数校验

  1. 自定义管道来验证 Params
    // src/modules/user/dto/getUserDetail.dto.ts
    import { Injectable, PipeTransform, BadRequestException } from '@nestjs/common';
    
    @Injectable()
    export class GetUserDetailDto implements PipeTransform {
      async transform(value: string) {
        const targetUserId = parseInt(value);
        if (targetUserId <= 0) {
          throw new BadRequestException('UsedId is positive number');
        }
        return targetUserId;
      }
    }
  2. 在控制器中使用封装好的管道验证参数
    import { Controller, Get, Param } from '@nestjs/common';
    import { GetUserDetailDto } from './dto/getUserDetail.dto';
    
    @Controller('users')
    export class UserController {
      @Get('getUserDetail/:id')
      getUserDetail(@Param('id', GetUserDetailDto) id: string): UserItem {
        return this.userService.getUserDetail(id);
      }
    }

设置状态码

在正常情况下,默认情况下,Nest.js会自动设置响应的状态码为 200。如果没有显式地设置状态码,Nest.js 会将响应的状态码设置为 200,表示请求成功。当使用class-validator库的装饰器对 DTO 进行校验时,如果校验失败,Nest.js 会自动抛出一个BadRequestException异常,状态码为 400,包含有关校验错误的详细信息。

那如果我想手动设置状态码呢?

通过 @HttpCode() 装饰器设置

// src/modules/user/user.controller.ts
import { Controller, Get, Param, HttpCode, HttpException, HttpStatus } from '@nestjs/common';
import { UserService } from './user.service';
import { GetUserDetailDto } from './dto/getUserDetail.dto';
import { UserItem } from 'src/core/types/user';

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

  @Get('getUserDetail/:id')
  @HttpCode(200)
  getUserDetail(
    @Param('id', GetUserDetailDto) id: string,
  ): UserItem | HttpException {
    const targetUser = this.userService.getUserDetail(id);
    if (targetUser) {
      return targetUser;
    } else {
      throw new HttpException('User was not found', HttpStatus.NOT_FOUND);
    }
  }
}

 通过 @Res() 参数装饰器设置

// src/modules/user/user.controller.ts
import { Controller, Get, Param, Res } from '@nestjs/common';
import { UserService } from './user.service';
import { GetUserDetailDto } from './dto/getUserDetail.dto';
import { Response } from 'express';

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

  @Get('getUserDetail/:id')
  getUserDetail(
    @Param('id', GetUserDetailDto) id: string,
    @Res() res: Response,
  ): void {
    const targetUser = this.userService.getUserDetail(id);
    if (targetUser) {
      res.status(200).json(targetUser);
    } else {
      res.status(404).json({ message: 'User was not found' });
    }
  }
}

两者的区别

  • @HttpCode() 装饰器用于设置控制器方法的返回状态码。它可以在控制器方法上直接使用,并指定所需的状态码。例如,@HttpCode(200) 将设置返回状态码为 200。这个装饰器只会影响返回的状态码,而不会对响应的内容进行其他处理。经常需要配合 HttpException 一起使用。

  • @Res() 参数装饰器用于将 Express 的 Response 对象注入到控制器方法中,以便你可以直接操作响应。通过 @Res(),你可以访问和修改响应的状态码、头部信息、发送响应等。这个装饰器提供了更灵活的方式来处理响应,并且你可以在控制器方法中进行更多的定制。 

统一响应体结构

Why?构建统一的响应体数据结构的好处是,它方便前端开发人员拦截和处理响应,提高代码可读性,并为错误处理提供统一的结构和信息,从而提升开发效率。

封装统一响应工具函数

// src/core/utils/resWrapper.util.ts
function RES_WRAPPER<T>(
  data: T,
  msg: string,
  code: number,
): Common.CommonRes<T> {
  return {
    data,
    msg,
    code,
  };
}
export function SUCCESS_RES<T>(data: T, msg = 'success') {
  return RES_WRAPPER(data, msg, 200);
}
export function ERROR_RES(msg: string, code = 400, data?: any) {
  return RES_WRAPPER(data, msg, code);
}
// src/modules/user/user.controller.ts
@Get('/getUserList')
getUserList(): Common.CommonRes<UserItem[]> {
  const userList = this.userService.getUserList();
  return SUCCESS_RES(userList, 'success');
}

  // src/modules/user/user.controller.ts
  @Get('getUserDetail/:id')
  getUserDetail(
    @Param('id', GetUserDetailDto) id: string,
    @Res() res: Response,
  ): void {
    const targetUser = this.userService.getUserDetail(id);
    if (targetUser) {
      res.status(200).json(SUCCESS_RES(targetUser));
    } else {
      res.status(404).json(ERROR_RES('User was not found'));
    }
  }

封装统一响应过滤器

  • 封装异常响应过滤器

import { Catch, HttpException, ExceptionFilter, ArgumentsHost } from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp(); // 获取请求上下文
    const response = ctx.getResponse(); // 获取请求上下文中的 response对象
    const status = exception.getStatus(); // 获取异常状态码

    // 设置错误信息
    const message = exception.message
      ? exception.message
      : `${status >= 500 ? 'Service Error' : 'Client Error'}`;
    const errorResponse = {
      data: {},
      message: message,
      code: -1,
    };

    // 设置返回的状态码,发送错误信息
    response.status(status).json(errorResponse);
  }
}
  • 正常响应拦截器

import { NestInterceptor, ExecutionContext, CallHandler, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data) => {
        return {
          data,
          code: 0,
          msg: '请求成功',
        };
      }),
    );
  }
}

在 main.ts 中全局注册

import { TransformInterceptor } from './path/to/your/interceptor';
import { HttpExceptionFilter } from './path/to/your/filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // 全局注册的响应过滤器
  app.useGlobalInterceptors(new TransformInterceptor());
  app.useGlobalFilters(new HttpExceptionFilter());

  await app.listen(3000);
}
bootstrap();

这样就可以统一的返回错误请求了,只需要抛出异常即可,比如:

throw new HttpException('User was not found', 404);

配置 Swagger API 文档

安装 & 配置

  • 安装 @nestjs/swagger swagger-ui-express

    npm install --save @nestjs/swagger swagger-ui-express
  • 在应用程序的根模块(main.ts)中配置 Swagger

    // src/main.ts
    import { NestFactory } from '@nestjs/core';
    import { ValidationPipe } from '@nestjs/common';
    import { AppModule } from './app.module';
    import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
    
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
    
      // 全局使用管道,用于 Controller 层参数校验
      app.useGlobalPipes(new ValidationPipe());
    
      const config = new DocumentBuilder()
        .setTitle('接口文档')
        .setDescription('Hello-nest 接口文档')
        .setVersion('1.0')
        .build();
      const document = SwaggerModule.createDocument(app, config);
      SwaggerModule.setup('swagger', app, document);
    
      await app.listen(3000);
    }
    bootstrap();

装饰器介绍

  • @ApiTags(tags: string[]): 用于给控制器或方法添加标签,用于组织和分类 API 文档

  • @ApiOperation(options: OperationOptions): 用于给方法添加操作信息,包括方法的摘要、描述、响应等

  • @ApiParam(options: ApiParamOptions): 用于给方法定义请求参数的描述信息

  • @ApiQuery(options: ApiQueryOptions): 用于给方法定义查询参数的描述信息

  • @ApiBody(options: ApiBodyOptions): 用于给方法定义请求体的内容

  • @ApiProperty(options: ApiPropertyOptions): 用于给模型定义各属性的描述信息

  • @ApiResponse(options: ApiResponseOptions): 用于给方法添加响应的描述信息

  • @ApiHeader(options: ApiHeaderOptions): 用于定义请求头的描述信息

  • 了解更多:https://github.com/nestjs/swagger

配置示例

// src/modules/user/user.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';

@Controller('users')
@ApiTags('用户相关接口')
export class UsersController {
  constructor(private readonly userService: UserService) {}

  @Post('addUser')
  @ApiOperation({
    summary: '获取用户列表',
    // description: '获取所有的用户列表',
  })
  @ApiResponse({
    status: 200,
    description: '成功返回200',
    schema: {
      type: 'array',
      example: [
        {
          id: 1,
          name: '张三',
          age: 18,
          gender: 1,
        },
      ],
    },
  })
  addUser(@Body() userData: AddUserDto): UserItem[] {
    return this.userService.addUser(userData);
  }
}
// src/modules/user/dto/addUser.dto.ts
import { IsNotEmpty, IsString, IsNumber, IsIn } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class AddUserDto {
  @IsNotEmpty({ message: 'id should not be empty' })
  @IsNumber({ allowNaN: false }, { message: 'id must be a number' })
  @ApiProperty({ example: 1, description: '用户唯一 id' })
  id: number;

  @IsNotEmpty()
  @IsString()
  @ApiProperty({ example: '张三', description: '用户名' })
  name: string;

  @IsNotEmpty()
  @IsNumber()
  @ApiProperty({ example: 18, description: '用户年龄' })
  age: number;

  @IsNotEmpty()
  @IsIn([1, 2])
  @ApiProperty({ example: 1, description: '用户性别: 1 -> 男、2 -> 女' })
  gender: 1 | 2;
}

配置完成访问:http://localhost:3000/swagger

 🚩 当然,如果你觉得 Swagger 界面不符合你风格的话,可以试试 https://app.apifox.com/

Nest.js 集成数据库

之前我们的数据都是直接存在 user.service.ts 的 private userList 中,如果要将数据存到数据库该怎么做?

在 Nest.js 中,与数据库集成可以通过各种数据库框架和库实现,如 TypeORM、Mongoose、Sequelize 等。这些框架提供了与不同类型的数据库进行交互的功能,并且与 Nest.js 集成非常方便。我们以 Mongoose 为例:

  1. 安装 Mongoose 和 @nestjs/mongoose:

    npm install mongoose @nestjs/mongoose
  2. 在 AppModule 中导入 MongooseModule 并配置连接:

    // src/app.module.ts
    import { Module } from '@nestjs/common';
    import { MongooseModule } from '@nestjs/mongoose';
    
    @Module({
      imports: [
        MongooseModule.forRoot('mongodb://localhost/nestjs', { // 链接自己服务器的数据库
          useNewUrlParser: true,
          useUnifiedTopology: true,
        }),
      ],
    })
    export class AppModule {}
  3. 为用户模块创建 Mongoose 模型和模式:

    // src/modules/user/schema/user.schema.ts -> 理解成配置数据库字段?
    import { Schema, Prop, SchemaFactory } from '@nestjs/mongoose';
    import { Document } from 'mongoose';
    
    @Schema()
    export class User extends Document {
      @Prop({ required: true })
      name: string;
    
      @Prop({ required: true })
      age: number;
    
      @Prop({ required: true })
      gender: 1 | 2;
    }
    
    export const UserSchema = SchemaFactory.createForClass(User);
  4. 在 user 模块引入 UserSchema,注册模型

    // src/modules/user/user.module.ts
    import { Module } from '@nestjs/common';
    import { UserController } from './user.controller';
    import { UserService } from './user.service';
    import { MongooseModule } from '@nestjs/mongoose';
    import { User, UserSchema } from './schema/user.schema';
    
    @Module({
      imports: [
        MongooseModule.forFeature([ // 注册模型
          { name: User.name, schema: UserSchema, collection: 'user' },
        ]),
      ],
      controllers: [UserController],
      providers: [UserService],
    })
    export class UserModule {}
  5. 改写 user.controller.ts 和 user.service.ts

    // src/modules/user/user.controller.ts
    @Get('/getUserList')
    async getUserList(): Promise<Common.CommonRes<UserItem[]>> {
      const userList = await this.userService.getUserList();
      return SUCCESS_RES(userList);
    }
    
    // src/modules/user/user.service.ts
    constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}
    async getUserList(): Promise<UserItem[]> {
      return this.userModel.find().exec();
    }

End 🎉

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

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

相关文章

EALLOW和EDIS指令的使用

1.EALLOW与EDIS 汇编指令 #define EALLOW __asm("EALLOW") #define EDIS __asm("EDIS") #define ESTOP0 __asm("ESTOP0")EALLOW(Edit allow)&#xff1a;防止cpu错误地写保护&#xff0c;ST1状态寄存器的EALLOW位显示是否允许写&#xff0c;0…

手机蓝牙功能测试点大全,都帮你总结好了

蓝牙是现在智能设备上一个必不可少的模块&#xff0c;支持大容量的近距离无线通信&#xff0c;典型的传输距离是10米左右&#xff0c;通过增加发射功率可达到100米&#xff0c;支持多链接&#xff0c;安全性高。 蓝牙工作在2.4GHZ的 ISM频段上&#xff0c;采用跳频扩谱技术避免…

微信小程序开发公司,小程序接单

小程序开发接单&#xff0c;公司开发&#xff0c;售后有保障&#xff0c;专属售后群对接&#xff0c;1对1 服务&#xff0c;下面是一些常见的小程序&#xff0c;全行业小程序都可开发 1.商城小程序 商城小程序是一种集购物、支付、订单管理等多种功能于一体的应用程序。为了使…

【使用OpenFeign在微服务中进行服务间通信】—— 每天一点小知识

&#x1f4a7; 使用 O p e n F e i g n 在微服务中进行服务间通信 \color{#FF1493}{使用OpenFeign在微服务中进行服务间通信} 使用OpenFeign在微服务中进行服务间通信&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微…

Python | cx_Oracle | DPI-1047报错处理(Win\Mac\Linux)

文章目录 Python | cx_Oracle | DPI-1047报错处理&#xff08;Win\Mac\Linux)测试代码报错信息解决步骤 Python | cx_Oracle | DPI-1047报错处理&#xff08;Win\Mac\Linux) 测试代码 import cx_Oracle# 建立与 Oracle 数据库的连接 connection cx_Oracle.connect("{lo…

【Redux】redux的使用详解

Redux的使用详解 Redux的核心思想 理解纯函数 1. 为什么要使用redux JavaScript开发的应用程序&#xff0c;已经变得越来越复杂了&#xff1a; JavaScript需要 管理的状态越来越多&#xff0c;越来越复杂&#xff1b;这些状态包括&#xff1a;服务器返回的数据、缓存数据、用户…

精细运营、数据赋能、生态联动…银行大零售数字化转型攻略

当前我国银行业的内外部环境正在发生深刻的变化&#xff0c;数字化转型已经成为行业的普遍共识。尤其在银行大零售数字化转型的过程中&#xff0c;各家银行差异性较大&#xff0c;虽然都开始突破传统零售模式&#xff0c;但仍面临一些共性的挑战&#xff0c;比如缺乏科学的顶层…

Java设计模式——装饰者模式

装饰者模式 一、概述 装饰者模式&#xff08;装饰器模式&#xff09;是一种结构型模式 定义&#xff1a; 在不改变现有对象结构的情况下&#xff0c;动态地给该对象增加一些额外职责&#xff08;功能&#xff09;的模式。 装饰者&#xff08;Decorator&#xff09;模式中的…

8年测试老鸟整理,接口自动化测试mock总结,这些你会多少?

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Mock原理/实现机制…

【youcans动手学模型】MobileNet 模型-CIFAR10图像分类

欢迎关注『youcans动手学模型』系列 本专栏内容和资源同步到 GitHub/youcans 【youcans动手学模型】MobileNet 模型-CIFAR10图像分类 1. MobileNet 卷积神经网络模型1.1 模型简介1.2 论文介绍 2. 在 PyTorch 中定义 MobileNet V1 模型类2.1 深度可分离卷积&#xff08;DSC&…

前端vue入门(纯代码)11

【11.全局事件总线(Global Event Bus)】 全局事件总线&#xff1a;实现任意组件间通信 【原理图】 结合上图&#xff0c;假若C组件想要传递数据给A组件&#xff0c;那么&#xff0c;就通过全局事件总线在A组件中绑定【$on】一个自定义事件demo&#xff0c;并界定一个参数来接…

如果我们20年后仍然在讨论Kubernetes,那将是技术上悲哀的时刻

这里是weihubeats,觉得文章不错可以关注公众号小奏技术&#xff0c;文章首发。拒绝营销号&#xff0c;拒绝标题党 Kelsey Hightower Google Cloud首席开发者 引子 在 Kubecon 上&#xff0c;Kelsey Hightower 曾经遇到过一个带有 Kubernetes 纹身的人&#xff0c;这让他开始…

工业相机的镜头如何选择?

相机的镜头如何计算&#xff0c;如果看公式的话&#xff0c;需要知道相机sensor的尺寸&#xff0c;相元的尺寸&#xff0c;计算起来数据也比较复杂&#xff0c;下面教大家一个简单的方法&#xff0c;就是如何借助镜头计算工具来使用。 巴斯勒相机的镜头选型地址 工业镜头选型…

我们该如何提升测试效率?

在大部分研发项目经理心中&#xff0c;进度往往会放在第一位&#xff0c;其次是成本&#xff0c;最后是质量&#xff0c;当然人员队伍最好也要稳定。天下武功&#xff0c;唯快不破&#xff1a;进度 > 成本 > 质量 > 人。 这个说法并不是绝对&#xff0c;今天我们并不是…

【Linux】线程同步条件变量

文章目录 一. 为什么要线程同步二. 条件变量1. 条件变量的使用2. 简单使用 结束语 一. 为什么要线程同步 通过互斥量&#xff0c;也就是加锁解锁&#xff0c;我们可以实现线程互斥&#xff0c;但是当访问的临界区代码较少时&#xff0c;线程执行会出现不停加锁解锁的情况。这样…

uniapp项目中使用vue3开发多端项目实践

目录 1、使用版本说明2、创建项目3、运行项目4、uniapppinia本地缓存插件PiniaPluginUnistorage5、 uni-app vue3 vite ts 项目结构 本项目中使用vue3开发多端项目实践&#xff0c;hbuilderx内置vue3模块&#xff0c;使用了vite4.x构建&#xff0c;编译构建项目的速度比火箭…

dpmsolver 论文核心整理

推导 DPM-Solver1的误差 由正文所述&#xff1a; 利用泰勒展开&#xff1a; B.3式就是换了个元 δ λ − λ s λ t − λ s \delta\frac{\lambda-\lambda_s}{\lambda_t-\lambda_s} δλt​−λs​λ−λs​​&#xff0c;代入论文公式(3.4)的积分项&#xff08;不含系数&…

一套完整的工厂车间现场管理指南,车间主管收藏好了!

车间是企业的基本层&#xff0c;搞好车间现场管理&#xff0c;有利于企业增强竞争力&#xff0c;提高产品质量和员工素质&#xff0c;保证安全生产&#xff0c;而车间班组长是生产线的主要管理者&#xff0c;是直接“当家人”&#xff0c;对生产现场状况了如指掌&#xff0c;对…

0001Java程序设计-SSM校园快递系统的设计与实现

摘 要 21世纪之后&#xff0c;全球信息化逐渐加快&#xff0c;尤其表现在近几年来电商行业的飞速发展&#xff0c;人们足不出户就可以买到自己想要的商品&#xff0c;尤其是青年大学生&#xff0c;追求新颖&#xff0c;更加乐忠于网络购物。网络购物的增加&#xff0c;就导致物…

【二】python爬虫进行AES解密遇到的问题

1、TypeError: Object type <class ‘str’> cannot be passed to C code 报错如下&#xff1a; File "C:\Python311\Lib\site-packages\Crypto\Util\_raw_api.py", line 143, in c_uint8_ptrraise TypeError("Object type %s cannot be passed to C cod…