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() : 用于应用管道。

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

相关推荐
神秘面具男2 小时前
HarmonyOS 6.0跨端远程控制
前端·后端
胡志辉2 小时前
从v8源码和react深入浅出理解 JavaScript 作用域链与闭包
前端·javascript
天蓝色的鱼鱼2 小时前
React Router v8 来了:react-router-dom 没了,老项目该怎么迁移?
前端·react.js
闪闪发光得欧3 小时前
前端提效新思路:Gemini 3.5 自动化定位 CSS 异常
前端·css
yingyima4 小时前
掌握正则表达式的核心:贪婪与非贪婪匹配的底层机制
前端
奇奇怪怪的4 小时前
文档摄入与 Chunking 策略全对决
前端
阳火锅4 小时前
😭测试小姐姐终于不骂我了!这个提BUG神器太香了...
前端·javascript·面试
道友可好5 小时前
AI 是最好的混乱放大器:代码熵管理实战
前端·人工智能·后端
猩猩程序员6 小时前
前端学习 AI Agent 开发
前端