NestJs使用教程

NestJs使用教程

DansRoh Lv4

前要

  • 此教程教你如何从0-1搭建一个NestJs的项目,封装全局拦截器,数据库连接,请求格式化处理,类验证器的使用,以及对于请求生命周期的理解
  • 学完本教程,你将知道NestJs一些常用模块的使用,如何编写后端接口,与数据库进行交互等
  • 对于 NestJs的一些基本概念 这一块的讲解,如果感到不熟悉,可以先跳过,在写代码的过程中去理解

什么是NestJs?

NestJS 是一个用于构建高效、可扩展的 Node.js 应用程序的开发框架。它基于 TypeScript 并且采用了面向对象的编程(OOP)、函数式编程和响应式编程的原则。NestJS 提供了一种结构清晰、模块化、可测试的方式来构建后端应用。
NestJS 提供了许多功能和工具,包括路由、中间件、数据验证、数据库集成、身份验证等。它还支持各种主流的 Node.js Web 框架,如 Express 和 Fastify,并提供了自己的抽象层以增强开发体验和性能。
总之,NestJS 通过结合现代的 JavaScript/TypeScript 特性和设计原则,为构建可维护、可扩展的服务端应用程序提供了一个强大的框架。

初始化NestJs项目

项目环境

  • node v20.1.0
  • nest v9.5.0

npm i -g @nestjs/cli // 全局安装nest脚手架
nest new project-name // 此时等待下载相关依赖
npm run start:dev // 启动
使用nest g res modules/article来创建模块

NestJs的一些基本概念

中间件

中间件用于处理进入应用程序的请求。中间件允许你在请求到达处理程序之前或响应离开处理程序之后执行额外的逻辑。
它可以:

  • 更改请求和响应对象
  • 在响应周期结束请求
  • 调用堆栈中的下一个中间件函数
  • 如果当前的中间件函数没有结束请求,它需要调用 nest() 来进入下一个中间件,否则请求无法正常完成

在 Nest 中,中间件是使用装饰器函数来创建的,并且可以应用于整个应用程序、控制器或特定的路由处理程序

装饰器

装饰器是一种js的语法糖,用于注解(annotate)类、方法、属性或参数,并为它们添加元数据或功能。
NestJS 中的装饰器提供了一种声明式的方式来定义和配置应用程序的各个部分。这些装饰器可以用于路由处理程序(Controllers)、服务(Services)、模块(Modules)、依赖注入(Dependency Injection)等。

守卫(Guards

  • 在NestJS中,守卫(Guards)是一种用于请求预处理的特殊类。
  • 守卫可以执行各种任务,例如身份验证、授权、角色检查等。它们可以帮助你确保只有经过身份验证的用户或具有特定权限的用户才能访问受保护的路由。
  • 要创建一个守卫,你可以使用@Injectable()装饰器创建一个类,并实现CanActivate接口。CanActivate接口包含一个canActivate()方法,该方法在每个请求到达被守卫的路由之前都会被调用。

拦截器(Interceptors)

  • 用于在处理请求之前和之后对请求进行转换、处理和修改。拦截器允许你在控制器方法之前或之后添加额外的逻辑,例如鉴权、日志记录、错误处理等。
  • 要创建一个拦截器,你可以使用@Injectable()装饰器将类标记为可注入的,并实现 NestInterceptor 接口。该接口定义了两个必须实现的方法:intercept() 和 handleRequest()。

管道(Pipes)

  • 管道允许你在将数据传递给路由处理程序之前对其进行转换、验证和处理。
  • NestJS 提供了多种类型的管道,包括内置管道和自定义管道。

内置管道

  1. ValidationPipe:用于执行基于类验证器(class-validator)库的验证。它可以自动将传入的数据转换为特定的类实例,并验证其属性的有效性。
  2. ParseIntPipe:将字符串转换为整数类型。
  3. ParseBoolPipe:将字符串转换为布尔类型。
  4. ParseArrayPipe:将字符串转换为数组类型。
    这些内置管道可通过在处理程序方法参数上使用装饰器来使用,例如 @Body(‘param’, ParseIntPipe)。

自定义管道

自定义管道必须实现 PipeTransform 接口并实现 transform 方法。transform 方法接收要处理的值作为参数,并返回经过转换后的值。

要在路由处理程序上使用管道,你可以使用 @UsePipes() 装饰器。例如:

1
2
3
4
5
@UsePipes(MyPipe)
@Post()
create(@Body() data: MyDto) {
// 处理逻辑
}

控制器(Controller)

  • 控制器是处理传入请求并返回响应的组件。它们负责定义路由、处理HTTP请求,并将结果返回给客户端。
  • 使用@Controller()装饰器来标记一个类作为控制器

提供者(Providers)

  • 在NestJS中,Providers是一个重要的概念,它们用于管理依赖注入和创建可共享的实例。
  • Provider(提供者)是一个普通的类,用于定义应用程序中的服务、库、数据库连接、外部API等。通过将这些提供者注册到模块中,NestJS可以自动处理它们之间的依赖关系,并在需要时创建实例。
  • 要创建一个提供者,你可以使用@Injectable()装饰器将一个类标记为可注入的
  • 通过使用提供者,你可以实现依赖注入、模块化和可测试性,使代码更加模块化和可重用。

创建 提供者(Providers):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
private cats = [];

create(cat: any) {
this.cats.push(cat);
}

findAll() {
return this.cats;
}
}

要在模块中注册提供者,可以使用providers属性。例如:

1
2
3
4
5
6
7
8
9
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}

异常过滤器

  • 异常过滤器是用来处理应用程序中出现的异常情况的机制。它允许你捕获并处理全局范围内或特定路由处理器中抛出的异常。

要创建一个异常过滤器,你需要实现 ExceptionFilter 接口并提供相应的逻辑来处理异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch()
export class MyExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();

// 处理异常逻辑
response.status(500).json({
message: 'Internal Server Error',
});
}
}

要将异常过滤器应用于整个应用程序或特定的控制器/路由处理器,你可以使用 @UseFilters() 装饰器。

1
2
3
4
5
6
7
8
9
10
11
import { Controller, Get, UseFilters } from '@nestjs/common';
import { MyExceptionFilter } from './my-exception.filter';

@UseFilters(MyExceptionFilter)
@Controller('example')
export class ExampleController {
@Get()
async getData() {
// 控制器逻辑
}
}

配置使用mysql

使用 TypeORM + Mysql 实现数据持久化。

安装相关软件

  1. 安装mysql
  2. 软件, 自行安装mysql可视化工具,可以下载vscode插件Database Client。调试请求使用Apifox或postman

项目配置

  • 安装依赖

npm install --save @nestjs/typeorm typeorm mysql2

  • 引用配置

在 app.module.ts 中引用 TypeOrmModule

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ArticleModule } from './modules/article/article.module';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
imports: [
// 使用 TypeORM 配置数据库
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: '888888',
database: 'test',
entities: ['dist/modules/**/*.entity{.ts,.js}'],
synchronize: true,
}),
ArticleModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

使用TypeORM

Entity

Entity 是由 @Entity 装饰器装饰的模型。 TypeORM 会为此类模型创建数据库表。

src/modules/article/entities/article.entity.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
VersionColumn,
} from 'typeorm';

@Entity()
export class Article {
// 主键id
@PrimaryGeneratedColumn()
id: number;

// 创建时间
@CreateDateColumn()
createTime: Date;

// 更新时间
@UpdateDateColumn()
updateTime: Date;

// 软删除
@Column({ default: false })
isDelete: boolean;

// 更新次数
@VersionColumn()
version: number;

// 文章标题
@Column('text')
title: string;

// 文章描述
@Column('text')
description: string;

// 文章内容
@Column()
content: string;
}

DTO

DTO(Data Transfer Object)是一种设计模式,用于在应用程序的不同层之间传输数据。DTO通常用于解耦应用程序的不同部分,并确保数据的一致性和可靠性。
DTO通常用于处理HTTP请求和响应的数据传输。当客户端发送请求时,请求中的数据需要进行验证、转换和处理,然后再传递给相应的服务或控制器进行处理。这时就可以使用DTO来定义数据的结构和规范。
创建DTO时,您可以定义所需的属性,并使用装饰器(如@ApiProperty)添加元数据,以指定每个属性的验证规则、数据类型和其他相关信息。这样,您可以明确指定要接收的数据的结构,并对其进行验证,以确保数据的有效性和完整性。

编写一个 创建article 的业务

创建DTO
src/modules/article/dto/create-article.dto.ts

1
2
3
4
5
export class CreateArticleDto {
readonly title: string;
readonly description: string;
readonly content: string;
}

在控制器使用DTO
src/modules/article/article.controller.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { ArticleService } from './article.service';
import { CreateArticleDto } from './dto/create-article.dto';
import { UpdateArticleDto } from './dto/update-article.dto';

@Controller('article')
export class ArticleController {
constructor(private articleService: ArticleService) {}

@Post()
create(@Body() createArticleDto: CreateArticleDto) {
return this.articleService.create(createArticleDto);
}

@Get()
findAll() {
return this.articleService.findAll();
}

@Get(':id')
findOne(@Param('id') id: string) {
return this.articleService.findOne(+id);
}

@Patch(':id')
update(@Param('id') id: string, @Body() updateArticleDto: UpdateArticleDto) {
return this.articleService.update(+id, updateArticleDto);
}

@Delete(':id')
remove(@Param('id') id: string) {
return this.articleService.remove(+id);
}
}

在module中定义使用的储存库
src/modules/article/article.module.ts

1
2
3
4
5
6
7
8
9
10
11
import { Module } from '@nestjs/common';
import { ArticleService } from './article.service';
import { ArticleController } from './article.controller';
import { Article } from './entities/article.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forFeature([Article])],
providers: [ArticleService],
controllers: [ArticleController],
})
export class ArticleModule {}

使用DTO和Entity实现create业务
src/modules/article/article.service.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { Injectable } from '@nestjs/common';
import { CreateArticleDto } from './dto/create-article.dto';
import { UpdateArticleDto } from './dto/update-article.dto';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Article } from './entities/article.entity';

@Injectable()
export class ArticleService {
constructor(
@InjectRepository(Article)
private readonly articleRepository: Repository<Article>,
) {}

async create(createArticleDto: CreateArticleDto): Promise<Article> {
const article = new Article();
article.title = createArticleDto.title;
article.description = createArticleDto.description;
article.content = createArticleDto.content;
const result = await this.articleRepository.save(article);
return result;
}

findAll() {
return `333`;
}

findOne(id: number) {
return `This action returns a #${id} article`;
}

update(id: number, updateArticleDto: UpdateArticleDto) {
return `This action updates a #${id} article`;
}

remove(id: number) {
return `This action removes a #${id} article`;
}
}

测试

此时,我们已经完成了 新建article的接口, 在运行npm run start:dev之后,我们的数据库就会新建一张article表,如下:
article表

这张表原本是没有数据,调用接口之口,就会插入一条数据
调用接口

封装拦截器、自定义Http异常、异常过滤器

自定义Http异常

新建文件src/exceptions/myHttpException.ts

1
2
3
4
5
6
7
import { HttpException } from '@nestjs/common';

export class MyHttpException extends HttpException {
constructor(message: string, code = 1001) {
super(message, code);
}
}

src/modules/article/article.service.ts中使用

1
2
3
4
5
6
7
async findOne(id: number) {
const result = await this.articleRepository.findOneBy({ id: id });
if (!result) {
throw new MyHttpException('没有查询到数据');
}
return result;
}

效果:
请求失败

封装拦截器

生成拦截器
nest g itc interceptor/transform
编辑拦截器src/interceptor/transform/transform.interceptor.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable, map } from 'rxjs';

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

在 main 中使用全局拦截器src/main.ts

1
2
3
4
5
6
7
8
9
10
11
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TransformInterceptor } from './interceptor/transform/transform.interceptor';

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

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

请求结果:
请求成功

封装异常过滤器

创建异常过滤器
nest g f filters/httpExecption
编辑src/filters/http-execption/http-execption.filter.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
} from '@nestjs/common';
import { Response } from 'express';

@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;
response.status(200).json({
code: status,
message,
});
}
}

在 main.ts 中全局使用异常过滤器 src/main.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TransformInterceptor } from './interceptor/transform/transform.interceptor';
import { HttpExceptionFilter } from './filters/http-execption/http-execption.filter';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new TransformInterceptor());
app.useGlobalFilters(new HttpExceptionFilter());

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

class-validator+类验证器的使用

简介

class-validator 是 Nest.js 中一个非常常用的第三方类验证器库,它可以用于验证 JavaScript 对象和 TypeScript 类实例的属性。它提供了许多内置的验证器和自定义验证器,例如字符串长度、最小值、最大值等等。您还可以创建自定义验证器来验证对象的属性。
在 Nest.js 中,您可以使用 class-validator 库来验证 DTO(数据传输对象)和模型类中的属性。它可以在控制器中验证从客户端传递过来的请求数据,或者在服务中验证业务逻辑中的数据。

安装依赖

npm i --save class-validator class-transformer

使用类验证器

src/main.ts 使用类验证器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TransformInterceptor } from './interceptor/transform/transform.interceptor';
import { HttpExceptionFilter } from './filters/http-execption/http-execption.filter';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new TransformInterceptor());
app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalPipes(new ValidationPipe());

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

修改异常过滤器,返回验证错误信息
src/filters/http-execption/http-execption.filter.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
} from '@nestjs/common';
import { Response } from 'express';

@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;

const exceptionResponse: any = exception.getResponse();
let validatorMessage = exceptionResponse;
if (typeof validatorMessage === 'object') {
validatorMessage = exceptionResponse.message[0];
}
response.status(200).json({
code: status,
message: validatorMessage || message,
});
}
}

使用class-validator进行验证

  • 新建工具类存放正则表达式

src/utils/regex.util.ts

1
2
// 非 0 正整数
export const regPositive = /^[1-9]\d*$/;
  • 修改DTO

src/modules/article/dto/create-article.dto.ts

1
2
3
4
5
6
7
8
9
10
11
12
import { IsNotEmpty } from 'class-validator';

export class CreateArticleDto {
@IsNotEmpty({ message: '请输入文字标题' })
readonly title: string;

@IsNotEmpty({ message: '请输入文字描述' })
readonly description: string;

@IsNotEmpty({ message: '请输入文字内容' })
readonly content: string;
}

至此,我们完成了新建文章的表单校验

class-validator测试

class-validator测试

NestJs请求的生命周期

在 Nest.js 中,请求的生命周期包括以下几个阶段:

  1. 接收请求:当客户端发送请求时,Nest.js 会接收并解析它。在这个阶段,Nest.js 将从请求中提取出所有的参数和数据,并将它们传递给适当的控制器或拦截器。
  2. 控制器处理:在接收到请求之后,Nest.js 将会将请求数据传递给适当的控制器。控制器将会根据请求的类型和路径,调用相应的方法来处理请求。在控制器方法中,您可以使用依赖注入来获取所需的服务和数据。
  3. 中间件处理:在控制器处理请求之前或之后,Nest.js 可以使用中间件对请求进行处理。中间件可以用于执行某些操作,例如身份验证、日志记录、异常处理等等。Nest.js 中的中间件与 Express 中的中间件类似,但是具有更好的类型安全性和可测试性。
  4. 拦截器处理:在控制器处理请求之前或之后,Nest.js 还可以使用拦截器对请求进行处理。拦截器可以用于对请求进行修改、日志记录、异常处理等等。它们可以被全局注册或局部使用,以便对特定的请求或响应进行处理。
  5. 响应处理:当控制器方法执行完毕后,Nest.js 将会使用响应对象来生成 HTTP 响应。在这个阶段,您可以使用拦截器来修改响应对象,或者使用异常过滤器来处理异常情况。
  6. 发送响应:在处理完响应之后,Nest.js 将会将响应发送回客户端。在这个阶段,您可以使用中间件来对响应进行处理,例如添加 CORS 头信息、压缩响应等等。

上图
生命周期

最后

至此,我们已经搭建完成一个基本的NestJs的项目,封装了全局拦截器,异常处理器,类验证器等模块
使用typeORM连接配置数据库,学会了如何创建数据库实体,如何定义传输数据DTO,编写了article的新增接口与查询接口
当然,NestJs不止能做这些,还有其他的好用的功能模块
推荐连接:

  • 标题: NestJs使用教程
  • 作者: DansRoh
  • 创建于 : 2023-07-06 00:00:00
  • 更新于 : 2024-05-08 17:13:02
  • 链接: https://blog.shinri.me/2023/07/06/02_NestJs使用教程/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论