NestJS 框架介绍
NestJS 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的框架。它利用 TypeScript 来开发强类型、可扩展的应用程序。NestJS 深受 Angular 框架的启发,因此它的架构、模块化和依赖注入系统与 Angular 有许多相似之处。以下是 NestJS 的一些核心特性和概念:
- 依赖注入(DI) :使用了一个强大的依赖注入系统,这是控制反转(
IoC)容器的一个实现。依赖注入允许你将服务(例如数据库连接、工具库等)注入到你的控制器、守卫、拦截器和服务中。 - 控制器 :控制器处理
HTTP请求和响应。它们通常用来定义路由和处理逻辑。 - 服务 :服务是应用程序的
业务逻辑层,通常用于封装和重用逻辑,可以被控制器或其他服务调用。 - 守卫(Guards) :守卫是特殊的类,可以用来预处理请求,例如
身份验证和授权。 - 拦截器(Interceptors) :拦截器用于拦截处理流程,可以在调用控制器之前或之后执行代码,例如
日志记录或错误处理。 - 装饰器 :广泛使用
JS的装饰器,它被用于类和方法上,提供了一种强大的方式来添加额外信息或行为。 - 中间件 :支持
Express、Fastify等 HTTP 平台的中间件,允许你在应用程序中使用现有的中间件。 - 插件和钩子 :提供了
生命周期钩子,允许你在应用程序的不同阶段执行代码。 - 微服务支持 :支持构建
微服务架构,提供了与消息传递系统(如MQTT或WebSockets)集成的工具。 - CQRS 和事件源 :支持命令查询责任分离(
CQRS)和事件源模式,这对于构建复杂的业务逻辑和数据一致性非常有用。 - 开箱即用 :内置了许多
开箱即用的解决方案,如数据库访问层、文件上传处理、缓存等。
NestJS 的设计理念是提供一个完整的解决方案,让开发者可以专注于构建业务逻辑,而不是花时间在底层架构上。
环境搭建
Node 和 Npm 环境安装
对于 NodeJs 和 Npm 的安装,我不多做介绍,可以参考以下链接:
也可以看我之前写的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.ts、app.module.ts 和 app.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 请求的处理器,该方法调用了 AppService 的 getHello 方法来获取响应字符串。
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 是用于封装和组织相关imports、controllers、providers的容器。这个文件就是相当于前端的某个页面模块容器。
每个模块都可以定义以下部分:
- 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 支持 Promise 和 Observable,你可以在控制器的动作中执行异步操作,如数据库查询或 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 等,常见的装饰器类型包括:
- 类装饰器:用于类级别,可以修改类的行为或添加元数据。
- 方法装饰器:用于方法级别,可以修改方法的行为或添加元数据。
- 属性装饰器:用于类属性级别,可以修改属性的行为或添加元数据。
- 参数装饰器:用于方法参数级别,可以访问或修改方法参数的信息。
NestJS 中的核心装饰器
- @Module() :用于定义模块。
- @Injectable() :用于标记服务类,使其能够由依赖注入系统管理和注入。
- @Controller() :用于定义路由的基类,可以指定路由前缀。
- @Get(), @Post(), @Put(), @Delete(), ... :这些装饰器用于定义路由处理器(
Route Handlers),指定处理HTTP请求的方法(如GET、POST等)和路径。 - @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 中,中间件分为两种类型:
- 全局中间件:可以在整个应用程序范围内生效。
- 局部中间件:仅在特定的路由或控制器中生效。
创建和使用中间件:
首先,我们需要创建一个中间件类。中间件类需要实现 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() : 用于应用管道。
虽然这些装饰器主要用于其他目的,但在某些情况下也可以与中间件一起使用来增强功能。