NestJS基础框架

NestJS 框架介绍

NestJS 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的框架。它利用 TypeScript 来开发强类型、可扩展的应用程序。NestJS 深受 Angular 框架的启发,因此它的架构、模块化和依赖注入系统与 Angular 有许多相似之处。以下是 NestJS 的一些核心特性和概念:

  1. 依赖注入(DI) :使用了一个强大的依赖注入系统,这是控制反转(IoC)容器的一个实现。依赖注入允许你将服务(例如数据库连接工具库等)注入到你的控制器、守卫、拦截器和服务中。
  2. 控制器 :控制器处理 HTTP 请求和响应。它们通常用来定义路由和处理逻辑
  3. 服务 :服务是应用程序的业务逻辑层,通常用于封装和重用逻辑,可以被控制器或其他服务调用。
  4. 守卫(Guards) :守卫是特殊的类,可以用来预处理请求,例如身份验证授权
  5. 拦截器(Interceptors) :拦截器用于拦截处理流程,可以在调用控制器之前或之后执行代码,例如日志记录错误处理
  6. 装饰器 :广泛使用 JS 的装饰器,它被用于类和方法上,提供了一种强大的方式来添加额外信息行为
  7. 中间件 :支持 ExpressFastify 等 HTTP 平台的中间件,允许你在应用程序中使用现有的中间件
  8. 插件和钩子 :提供了生命周期钩子,允许你在应用程序的不同阶段执行代码。
  9. 微服务支持 :支持构建微服务架构,提供了与消息传递系统(如 MQTTWebSockets)集成的工具。
  10. CQRS 和事件源 :支持命令查询责任分离(CQRS)和事件源模式,这对于构建复杂的业务逻辑和数据一致性非常有用。
  11. 开箱即用 :内置了许多开箱即用的解决方案,如数据库访问层文件上传处理缓存等。

NestJS 的设计理念是提供一个完整的解决方案,让开发者可以专注于构建业务逻辑,而不是花时间在底层架构上。

环境搭建

Node 和 Npm 环境安装

对于 NodeJsNpm 的安装,我不多做介绍,可以参考以下链接:

也可以看我之前写的NodeJS-基础学习

安装 NestJS CLI 工具

使用 CLI 工具(@nestjs/cli)快速创建新项目。

shell 复制代码
## 安装全局 NestJS CLI 工具
npm i -g @nestjs/cli

## 安装你的项目
nest new project-name

项目结构

默认生成的项目结构如下:

java 复制代码
.
├── README.md // 项目说明
├── nest-cli.json // nest 命令文件,用于构建项目。
├── package-lock.json
├── package.json // 项目依赖包
├── src
│   ├── app.controller.spec.ts // 针对控制器的单元测试。
│   ├── app.controller.ts // 带有单个路由的基本控制器。
│   ├── app.module.ts // 应用程序的根模块(root module)。
│   ├── app.service.ts // 具有单一方法的基本服务(service)。
│   └── main.ts // 应用程序的入口文件,它使用核心函数 NestFactory 来创建 Nest 应用程序的实例。
├── test // 测试文件
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── tsconfig.build.json // tsconfig 文件,用于构建项目。
└── tsconfig.json

项目运行

下面命令将使用 HTTP 服务器启动应用程序,以侦听 src/main.ts 文件中所定义的端口。应用程序运行后,打开浏览器并访问 http://localhost:3000/ 地址,将看到类似 Hello World! 的信息。

arduino 复制代码
npm run start

MVC经典架构解析

NestJS 中,app.controller.tsapp.module.tsapp.service.ts是三个基础且关键的文件,它们分别代表了控制器(Controller)、模块(Module)和服务(Service)的概念。这些文件共同协作,构成了NestJS应用的基础结构。这也是经典的MVC架构,下面我们逐一解析他们的作用。

app.controller.ts

控制器是处理传入请求返回响应的部分。它通常包含了一组路由处理器(路由装饰器如@Get(), @Post(), @Put(), @Delete()等),这些处理器将特定的请求映射到处理函数上。

typescript 复制代码
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

// 定义为一个控制器
@Controller()
export class AppController {
  // 构造函数:注入 appService
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    // get 请求/返回 appService 的 getHello 方法
    return this.appService.getHello();
  }
}

AppController 类通过 @Controller() 装饰器定义为一个控制器,并且它依赖于 AppService 服务(通过构造函数注入)。@Get() 装饰器将 getHello 方法映射为 GET 请求的处理器,该方法调用了 AppServicegetHello 方法来获取响应字符串。

app.module.ts

模块是 NestJS 中用于封装和组织相关控制器服务、提供者( providers,如服务)和导入( imports,即其他模块)的容器。

typescript 复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

// 通过@Module()装饰器定义为一个模块
@Module({
  imports: [], // 其他模块
  controllers: [AppController], // 控制器
  providers: [AppService], // 提供者,如服务
})
export class AppModule {}

AppModule 类通过 @Module() 装饰器定义为一个模块。它指定了当前模块包含的控制器( controllers 数组中的AppController)和提供者( providers 数组中的 AppService)。由于这是一个根模块,所以 imports 数组是空的。

app.service.ts

服务是 NestJS 中用于封装业务逻辑的部分。它们可以被注入到控制器或其他服务中,以便在多个地方重用逻辑。

kotlin 复制代码
import { Injectable } from '@nestjs/common';

// 定义为一个可注入的服务
@Injectable()
export class AppService {
  // 一般,定义数据库增删改查的方法
  getHello(): string {
    return 'Hello World!';
  }
}

AppService 类通过 @Injectable() 装饰器定义为一个可注入的服务。它提供了一个 getHello 方法,该方法返回一个简单的字符串" Hello World! "。这个服务可以在控制器中被注入并调用,以返回响应给客户端。

基本概念

模块(Modules)

通过上面的 app.module.ts,我们可以知道 Modules 是用于封装和组织相关importscontrollersproviders的容器。这个文件就是相当于前端的某个页面模块容器。

每个模块都可以定义以下部分:

  • controllers:处理传入请求并返回响应的组件。
  • providers:封装业务逻辑和数据的组件,通常是服务。
  • imports:当前模块需要依赖的其他模块列表。
  • exports:当前模块希望导出以便其他模块使用的提供者(服务、组件等)列表。

Modules示例

假设我们需要做一个用户管理的功能,我们可以创建一个名为 UserModule 的特性模块。

typescript 复制代码
// User.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './User.controller';
import { UserService } from './User.service';

@Module({
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService], // 可以不写,如果其他模块需要 UserService,则导出它
})
export class UserModule {}

AppModule 中可以导入 UserModule,在整个应用程序中被访问和使用。

控制器(Controllers)

控制器的作用是如何定义路由和处理请求。控制器负责将请求路由到相应的处理函数,并可能调用服务(Services)来执行实际的业务逻辑。

控制器的基本概念

  • 路由 :控制器通过装饰器(如@Get(), @Post(), @Put(), @Delete()等)将特定的HTTP请求方法映射到处理函数上。这些装饰器定义了请求的 URL 路径、请求方法以及任何请求参数。
  • 动作 :控制器中的处理函数被称为动作。当请求与路由匹配时,相应的动作将被调用,并可以执行诸如验证请求调用服务返回响应等操作。
  • 响应 :动作可以返回一个、一个对象、一个 Promise、一个 Observable 或是一个流(Stream),NestJS 会自动将其序列化为 JSON(对于对象、Promise和Observable)并发送回客户端。
typescript 复制代码
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

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

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    // 调用服务来创建用户
    return this.userService.create(createUserDto);
  }

  @Get()
  findAll(@Query() query: { name: string; page: number; pageSize: number }) {
    return this.userService.findAll(query);
  }

  // 其他动作...
}

此外,NestJS 支持 PromiseObservable,你可以在控制器的动作中执行异步操作,如数据库查询HTTP 请求。NestJS会自动等待 Promise 解析或 Observable 完成,并将结果返回给客户端。

服务(Services)

服务(Services)是封装了特定业务逻辑和数据的组件。它们通常用于处理应用程序中需要执行的任务,如数据访问验证转换等。服务通常不直接处理 HTTP 请求,而是由控制器(Controllers)调用以完成特定的业务操作。

可以使用@Injectable()装饰器定义一个类来创建服务:

typescript 复制代码
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Tags } from './entities/tags.entity';
import { Like, Repository } from 'typeorm';

@Injectable()
export class UserService {
  // 注入 userRepository 用户数据库
  constructor(
    @InjectRepository(User) private readonly userRepository: Repository<User>,
  ) {}

  // 创建用户
  create(createUserDto: CreateUserDto) {
    const data = new User();
    data.name = createUserDto.name;
    data.desc = createUserDto.desc;

    return this.userRepository.save(data);
  }

  // 查询所有
  async findAll(query: { name: string; page: number; pageSize: number }) {
    const data = await this.userRepository.find({
      where: {
        name: Like(`%${query.name}%`),
      },
      order: {
        id: 'DESC',
      },
      skip: (query.page - 1) * query.pageSize,
      take: query.pageSize,
    });

    const total = await this.userRepository.count({
      where: {
        name: Like(`%${query.name}%`),
      },
    });

    return {
      data,
      total,
    };
  }

  // ...其他方法
}

服务通常负责以下职责:

  • 数据访问 :服务可以封装与数据库、文件系统或其他数据源交互的代码。
  • 业务逻辑:服务可以包含执行特定业务规则和操作的代码。
  • 转换和验证:服务可以对输入数据进行验证,并将内部数据转换为适合外部使用的格式。
  • 依赖管理:服务可以管理对其他服务的依赖,并将它们封装在单个接口中,以简化组件之间的交互。

依赖注入(DI)

依赖注入(Dependency Injection,简称 DI)是一种软件设计模式,它允许一个类(或对象)的依赖项在创建时或运行时被外部传入,而不是在类内部自行创建。

依赖注入的几种方式:

  • 构造函数注入 :通过类的构造函数传入依赖项。
  • Setter方法注入 :通过类的 setter 方法传入依赖项。
  • 接口注入:这种方式较少使用,主要通过接口来定义依赖项,并在实现类中注入这些依赖项。

装饰器(Decorators)

在 NestJS 大量使用的装饰器,如 @Controller, @Get, @Post 等,常见的装饰器类型包括:

  1. 类装饰器:用于类级别,可以修改类的行为或添加元数据。
  2. 方法装饰器:用于方法级别,可以修改方法的行为或添加元数据。
  3. 属性装饰器:用于类属性级别,可以修改属性的行为或添加元数据。
  4. 参数装饰器:用于方法参数级别,可以访问或修改方法参数的信息。

NestJS 中的核心装饰器

  • @Module() :用于定义模块。
  • @Injectable() :用于标记服务类,使其能够由依赖注入系统管理和注入。
  • @Controller() :用于定义路由的基类,可以指定路由前缀。
  • @Get(), @Post(), @Put(), @Delete(), ... :这些装饰器用于定义路由处理器(Route Handlers),指定处理 HTTP 请求的方法(如 GETPOST 等)和路径。
  • @Inject()、InjectRepository() :用于在构造函数中注入依赖项。虽然 @Injectable() 已经为服务类启用了依赖注入,但 @Inject() 允许你更具体地控制注入过程,特别是在处理非类依赖项时。
  • @UseGuards() :用于在路由处理器或整个控制器上应用守卫(Guards)。守卫用于实现认证、授权等安全机制。

自定义装饰器

NestJS 也支持自定义装饰器,允许你根据需要定义自己的装饰器逻辑。自定义装饰器通常用于添加自定义元数据或修改类的行为。

typescript 复制代码
const Log = (target: Object, key: string | symbol, descriptor: TypedPropertyDescriptor<any>) => {
  const originalMethod = descriptor.value;

  descriptor.value = function(...args: any[]): any {
    console.log(`Calling ${key.toString()} with args`, args);
    const result = originalMethod.apply(this, args);
    console.log(`Called ${key.toString()}, result is ${result}`);
    return result;
  };

  return descriptor;
};

@Log
methodToLog() {
  // 方法体
}

NestJS 中,装饰器是实现模块化、依赖注入和面向切面编程等关键概念的重要工具。它们极大地提高了代码的可读性、可维护性和可扩展性。

进阶特性

中间件(Middleware)

中间件主要用于全局请求处理,可以用于日志记录错误处理身份验证等功能。

NestJS 中,中间件分为两种类型:

  1. 全局中间件:可以在整个应用程序范围内生效。
  2. 局部中间件:仅在特定的路由或控制器中生效。

创建和使用中间件:

首先,我们需要创建一个中间件类。中间件类需要实现 NestMiddleware 接口,并且必须有一个 use 方法来处理 HTTP 请求和响应。

typescript 复制代码
// 创建中间件
import { NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`Request received at ${req.url}`);
    next(); // 调用 next() 来传递控制权给下一个中间件或路由处理器
  }
}

要将中间件应用于整个应用程序,需要在主模块(通常是 AppModule)中注册中间件。

typescript 复制代码
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggerMiddleware } from './middlewares/logger.middleware';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*'); // 应用于所有路由
  }
}

如果只想在特定的路由或控制器中使用中间件,可以在路由定义时指定。

typescript 复制代码
import { Controller, Get, UseMiddleware } from '@nestjs/common';
import { AppService } from './app.service';
import { LoggerMiddleware } from './middlewares/logger.middleware';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  @UseMiddleware(LoggerMiddleware) // 应用于特定的路由
  getHello(): string {
    return this.appService.getHello();
  }
}

NestJS 还提供了一些装饰器来更方便地应用中间件:

  • @UseInterceptors() : 用于应用拦截器。
  • @UseGuards() : 用于应用守卫。
  • @UsePipes() : 用于应用管道。

虽然这些装饰器主要用于其他目的,但在某些情况下也可以与中间件一起使用来增强功能。

相关推荐
To_OC3 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic3 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy3 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志4 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble5 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯6 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜6 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司7 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
江华森7 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http
IT_陈寒8 小时前
Redis的KEYS命令把我搞崩溃了,改用SCAN才活过来
前端·人工智能·后端