NestJS 是一个用于构建高效可靠可扩展服务器端应用程序的框架。它使用 TypeScript 编写,但也可以使用纯 JavaScript 编写。NestJS 提供了一个结构化的方法来编写模块化代码,并且它内置了对现代 JavaScript 框架和库的支持,如 ExpressFastify

NestJS的主要特点包括:

  1. 模块化NestJS 应用程序由模块组成,每个模块都有其自己的作用域和职责。这有助于保持代码的组织和可维护性。
  2. 依赖注入NestJS 利用 TypeScript元数据功能来提供依赖注入,这有助于创建松耦合可测试的代码。
  3. 装饰器NestJS 使用装饰器来简化常见的任务,如路由中间件控制器等。
  4. 微服务架构NestJS 支持微服务架构,可以轻松地与消息队列数据库其他服务进行通信
  5. 安全性NestJS 提供了内置的安全性支持,包括身份验证授权
  6. 测试NestJS 有一个强大的测试框架,可以轻松编写单元测试端到端测试
  7. 社区和生态系统NestJS 有一个活跃的社区,提供了许多模块和插件来扩展其功能。

环境搭建

安装nestjs

1
npm i -g @nestjs/cli

创建项目

1
nest new project-name

运行项目

1
2
cd project-name
npm run start

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
project-name
├── src
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test
│ └── app.e2e-spec.ts
├── package.json
├── README.md
├── tsconfig.json
└── tslint.json
  1. app.controller.ts: 控制器,处理 HTTP 请求。
  2. app.module.ts: 模块,定义了应用的各个部分,包括控制器、服务、管道、守卫等。
  3. app.service.ts: 服务处理业务逻辑。
  4. main.ts: 入口文件,启动应用。
  5. test/app.e2e-spec.ts: 端到端测试文件。
  6. package.json: 项目依赖
  7. README.md: 项目说明文件
  8. tsconfig.json: 编译器配置文件。
  9. tslint.json: 代码风格检查配置文件。

项目规范结构

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
44
45
46
47
48
49
50
nodejs
├── package.json
├── README.md
├── src
│ │ └── constants(全局常量定义)
│ │ ├──common.constants.ts
│ │ └── utils(常用工具类)
│ │ ├──http.util.ts
│ │ └──file.util.ts
│ ├── app.module.ts(模块配置文件)
│ ├── common (通用模块,包含自定义装饰器、过滤器、守卫、拦截器、中间件)
│ │ ├── decorators (项目通用装饰器)
│ │ │ └── roles.decorator.ts
│ │ ├── filters (过滤器)
│ │ │ └── http-exception.filter.ts
│ │ ├── guards (守卫)
│ │ │ └── roles.guard.ts
│ │ ├── interceptors (拦截器)
│ │ │ ├── exception.interceptor.ts
│ │ │ ├── logging.interceptor.ts
│ │ ├── middleware (中间件)
│ │ │ └── logger.middleware.ts
│ │ └── pipes (管道,主要用于数据验证和类型转换)
│ │ ├── parse-int.pipe.ts
│ │ └── validation.pipe.ts
│ ├── config (配置文件信息)
│ │ ├── database.ts
│ │ ├── redis.ts
│ ├── jobs (高并发场景下队列处理)
│ ├── main.ts (入口文件)
│ ├── modules (业务代码,按目录区分模块)
│ │ ├── hello
│ │ │ ├── hello.controller.ts
│ │ │ ├── hello.module.ts
│ │ │ └── hello.service.ts
│ │ └── users
│ │ │ ├── dto (数据传输对象定义)
│ │ │ │ └── users.create.dto.ts
│ │ │ │ └── users.update.dto.ts
│ │ ├── users.controller.ts (控制层)
│ │ ├── users.entity.ts (映射数据库模型对象)
│ │ ├── users.module.ts (模块定义)
│ │ └── users.service.ts (service层)
│ ├── tasks (定时任务)
│ │ ├── tasks.module.ts
│ │ └── tasks.service.ts
│ └── templates (页面模板)
├── test (单元测试)
│ ├── app.e2e-spec.ts
├── tsconfig.json

nestjs常用命令

命令描述
nest new project-name创建一个新的NestJS项目。
nest generate module module-name创建一个新的模块
nest generate controller controller-name创建一个新的控制器
nest generate service service-name创建一个新的服务
nest start启动NestJS项目。
nest build编译NestJS项目。
nest start --watch启动NestJS项目并监听文件变化。
nest start --debug启动NestJS项目并进入调试模式。
nest start --watch --debug启动NestJS项目并监听文件变化并进入调试模式。
nest generate class class-name创建一个新的
nest generate interface-name创建一个新的接口
nest g cl class-name创建一个新的
nest g interface interface-name创建一个新的接口
nest g mo module-name创建一个新的模块
nest g co controller-name创建一个新的控制器
nest g s service-name创建一个新的服务
nest g middleware middleware-name创建一个新的中间件
nest g pipe pipe-name创建一个新的管道
nest g guard guard-name创建一个新的守卫
nest g decorator decorator-name创建一个新的装饰器
nest g interface interface-name创建一个新的接口
nest g exception exception-name创建一个新的异常过滤器
nest g filter filter-name创建一个新的过滤器
nest g interceptor interceptor-name创建一个新的拦截器
nest g dto dto-name创建一个新的数据传输对象
nest g interface interface-name创建一个新的接口
nest g module module-name创建一个新的模块
nest g provider provider-name创建一个新的提供者
nest g resolver resolver-name创建一个新的解析器
nest g resource resource-name创建一个新的crud资源

装饰器

类装饰器

装饰器描述
@Controller用于定义一个控制器类,它将处理客户端请求并返回响应
@Module用于定义一个模块,它将组织应用程序的结构,包括控制器、提供者、守卫等。
@Injectable用于定义一个提供者,它将被NestJS的依赖注入系统管理。
@Middleware用于定义一个中间件,它将在请求处理管道中执行
@Interceptor用于定义一个拦截器,它可以在方法执行前后添加额外的逻辑
@Pipe用于定义一个管道,它可以在数据从控制器方法返回到客户端之前转换数据
@Guard用于定义一个守卫,它可以在路由处理之前执行,用于权限验证
@ExceptionFilter用于定义一个异常过滤器,它可以在捕获到异常时执行用于处理错误
@Transform用于定义一个转换器,它可以在数据从控制器方法返回到客户端之前转换数据
@Subscription用于定义一个订阅,它将处理WebSocket消息
@Component用于定义一个组件,它将被NestJS的依赖注入系统管理
@Injectable用于定义一个提供者,它将被NestJS的依赖注入系统管理

方法装饰器

NestJS中,方法装饰器主要用于增强方法的行为,例如路由处理日志记录权限验证等。以下是一些常用的NestJS方法装饰器:

装饰器描述
@Get()用于定义一个HTTP GET请求的路由处理方法。
@Post()用于定义一个HTTP POST请求的路由处理方法。
@Put()用于定义一个HTTP PUT请求的路由处理方法。
@Delete()用于定义一个HTTP DELETE请求的路由处理方法。
@Patch()用于定义一个HTTP PATCH请求的路由处理方法。
@Options()用于定义一个HTTP OPTIONS请求的路由处理方法。
@Head()用于定义一个HTTP HEAD请求的路由处理方法。
@All()用于定义一个可以处理所有HTTP方法的路由处理方法。
@Param()用于提取路由参数,通常与@Get()@Post()等装饰器一起使用。
@Body()用于提取请求体中的数据,通常用于处理POST或PUT请求
@Query()用于提取查询字符串参数。
@Headers()用于提取请求头信息。
@Session()用于获取会话信息。
@Ip()用于获取客户端的IP地址。
@HostParam()用于获取请求的主机名。
@User()用于获取当前用户信息,通常与身份验证结合使用。
@Req()用于获取原始的请求对象。
@Res()用于获取原始的响应对象。
@Next()用于获取下一个中间件函数。
@HttpCode()用于设置响应的状态码。
@Header()用于设置响应头。
@UseGuards()用于添加守卫,用于权限验证。
@UseInterceptors()用于添加拦截器,用于在方法执行前后添加额外的逻辑。
@UseFilters()用于添加过滤器,用于处理异常或修改响应。

参数装饰器

装饰器描述
@Request() 或 @Req()注入当前的HTTP请求对象
@Response() 或 @Res()注入当前的HTTP响应对象。
@Next()注入一个函数,调用它将执行下一个中间件
@Session()注入当前的会话对象
@Param(param?: string)注入路由参数,例如 @Param('id') id: string
@Body(param?: string)注入请求体,例如 @Body('user') user: UserEntity。
@Query(param?: string)注入查询字符串参数,例如 @Query('sort') sort: string。
@Headers(param?: string)注入请求头信息,例如@Headers('Content-Type') contentType: string。
@Ip()注入客户端的IP地址
@HostParam()注入请求的主机名
@User()注入当前用户信息通常与身份验证结合使用
@HttpCode()设置响应的状态码
@Header()设置响应头
@Session()注入当前的会话对象
@UploadedFile()注入上传的文件对象
@UploadedFiles()注入上传的文件数组

接受参数

@Get

http://localhost:3000/findOne

1
2
3
4
@Get("/findOne")
findOne(): string{
return "这是findOne方法!";
}

Get 动态路由参数
http://localhost:3000/findOne/1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Get("/findOne/:id")
findOne(@Param("id") id: string): string{
return `这是findOne方法! id=${id}`;
}

@Get("/findOne/:id")
findOne(@Param() params: object): string{
// {id: '124124'}
return `这是findOne方法! dto=${params}`;
}

@Get("/findOne/:id")
findOne(@Query("id") id: string): string{
// 124124
return `这是findOne方法! id=${id}`;
}

@Post

http://localhost:3000/create

1
2
3
4
@Post("/create")
create(@Body() createCatDto: CreateCatDto): string{
return `这是create方法! createCatDto=${createCatDto}`;
}

@Query

http://localhost:3000/user/test/query?id=124124&name=张三

1
2
3
4
5
@Get('/test/query')
findOneByQuery(@Query() query: any) {
console.log(query); // { id: '124124', name: '张三' }
return query;
}

@Request

http://localhost:3000/user/test/query?id=124124&name=张三

1
2
3
4
5
@Get('req/query')
findOne(@Req() req: Request): string {
console.log(req.query); // { id: '124124', name: '张三' }
return req.query; // req.params req.body req.headers req.session req.ip req.hostname
}

配置静态资源

NestJS中,你可以通过配置静态资源来提供文件,如图片CSSJavaScript等。NestJS使用了内置的ExpressFastify服务器,因此你可以使用它们的中间件来配置静态资源。

  1. 安装所需的包:确保你的项目中安装了@nestjs/platform-express包,因为NestJS默认使用Express作为其HTTP平台。
    1
    npm install --save @nestjs/platform-express
  2. 在模块中main.ts文件中,导入静态资源中间件:在你的模块文件中,导入并使用NestFastifyApplication的register方法来配置静态资源。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import { NestExpressApplication } from '@nestjs/platform-express';
    import { join } from 'path';
    const assetDir = (dir) => join(__dirname, '..', dir); // 根目录
    async function bootstrap() {
    const app = await NestFactory.create<NestExpressApplication>(AppModule);
    // http://localhost:3000/image/img1.jpg
    // app.useStaticAssets('public'); // 配置静态资源目录
    // 指定路径别名前缀 访问 /assets/ 的时候 实际访问的是 /public/
    // http://localhost:3000/assets/image/img1.jpg
    app.useStaticAssets(assetDir('public'), {
    prefix: '/assets/',
    }); // 配置静态资源目录
    await app.listen(3000);
    }
    bootstrap();

    在上面的例子中,path.join(__dirname, 'public')包含静态资源的目录的绝对路径{ prefix: '/assets/' }是一个可选的配置对象,用于设置静态资源的URL前缀。

  3. 创建静态资源目录:在你的项目根目录下创建一个名为public/image/xxx.jpg的文件夹,并将你的静态资源文件(如图片CSSJavaScript等)放入该文件夹。

  4. 访问静态资源:在浏览器中访问http://localhost:3000/image/xxx.jpg!

    如果配置了prefix前缀的话,那么访问的路径则应该是http://localhost:3000/assets/image/img1.jpg

配置模版引擎

  1. 安装模版引擎
    1
    npm install --save ejs
  2. main.ts模块儿中设置模版引擎(app.setViewEngine('ejs'))
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import { NestExpressApplication } from '@nestjs/platform-express';
    import { join } from 'path';
    const assetDir = (dir) => join(__dirname, '..', dir); // 根目录
    async function bootstrap() {
    const app = await NestFactory.create<NestExpressApplication>(AppModule);
    // app.useStaticAssets('public'); // 配置静态资源目录
    // 指定路径别名 访问 /assets/ 的时候 实际访问的是 /public/
    // http://localhost:3000/assets/image/img1.jpg
    app.useStaticAssets(assetDir('public'), {
    prefix: '/assets/',
    }); // 配置静态资源目录

    // 配置模板引擎
    app.setViewEngine('ejs');
    await app.listen(3000);
    }
    bootstrap();
  3. 根目录中创建 views目录,并在views目录下创建/default/index.ejs文件!
    文件内容如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>
    <center><h3>模版引擎测试</h3></center>
    <center>
    <p>Hello, Nest.js!</p>
    <p>This is a simple demo of Nest.js template engine.</p>
    <p>名称: <%=name%></p>
    <p>年龄: <%=age%></p>
    </center>
    </body>
    </html>
  4. controller中通过@Render()来引入模版引擎!
    1
    2
    3
    4
    5
    @Get("/getTemplate")
    @Render("default/index")
    getTemplate() : object {
    return { name: "张三", age: 18 };
    }
  5. 访问http://localhost:3000/getTemplate即可看到模版引擎渲染的页面。

配置session

众所周知,HTTP协议是无状态的,这意味着每次浏览器请求服务器的时候,服务器并不知道这个请求与之前的请求是否来自同一个用户。这就像你每次到咖啡店,咖啡师都不记得你喜欢什么口味的咖啡一样。

为了解决这个问题,Session被设计来跟踪保持用户的状态Session可以被理解为服务器端存储的一块空间,每个用户都有一个独立的Session空间,用来保存用户相关的信息,比如用户的登录状态、购物车中的商品等。

当用户首次访问服务器时,服务器会为此用户创建一个独一无二的Session,并生成一个唯一的标识符(通常称为session ID)。随后,这个session ID会被存储在用户浏览器的cookie中。此后,用户的每次请求中都会携带这个session ID,服务器通过这个ID获取对应的Session信息,以识别用户身份

  1. 身份验证:通过Session检查用户是否登录,并获取用户的登录信息
  2. 状态保持:无论用户在站点中浏览哪里,都能保持其特定的交互状态,如购物车数据用户设置
  3. 安全:Session可以在服务器端进行加密处理保证敏感信息的安全

安装插件

1
2
npm install express-session --save
npm install @types/express-session --save-dev

main.ts中引入session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import * as session from "express-session";
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.use(session({
secret: "ppxiong", // 生成服务端 session 签名
// resave: false,
// saveUninitialized: false,
name: "ppxiong.sid", // 生成客户端cookie的名字 默认connect.sid
cookie: {
maxAge: 60000 // Cookie的过期时间(单位毫秒)
},
rolling: true, // 滚动过期时间 每次请求重新设置,并重置cookie过期时间
}))
}

bootstrap()

session验证码案例

安装svg-captcha

svg-captcha 是一款验证码插件!

1
npm install svg-captcha --save

controller中使用

http://localhost:3000/user/code
http://localhost:3000/user/login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建验证码
@Get('/code')
createCode(@Req() req, @Res() res, @Session() session) {
const captcha = SvgCaptcha.create({
size: 4,
fontSize: 50,
width: 100,
height: 40,
background: '#f0f0f0',
noise: 2,
color: true,
ignoreChars: '0o1i',
// noiseColor: '#fff',
});
session.code = captcha.text; // 存储验证码到 session 上
res.type('application/json');
console.log("createCode!!!!!!!!!!!!!!!!", captcha.text)
res.send({code: captcha.text, image: captcha.data}); // 返回验证码图片
}
1
2
3
4
5
6
7
8
// 登录
@Post('/login')
login(@Body() body, @Res() res, @Session() session, ) {
const { username, password, code } = body;
const { code: sessionCode } = session;
res.type('application/json');
res.send({ code: 200, message: '请求成功!', success: true })
}

提供者(service)

NestJS 中,"提供者"Providers)是一个核心概念,它指的是可以被注入到其他组件中的任何类。提供者可以是服务(Service)、存储库(Repository)、工厂(Factory)、助手(Helper)等。它们通常用于封装业务逻辑数据访问第三方服务集成等。

具体在xxx.module.ts文件中 @Module({providers: [xxxService]}) 配置!
provide: 提供者的 name! 可以是 [useClass useValue, useFactory]!

基本使用

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

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

findAll(): string[] {
return this.cats;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';

@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}

@Get()
async findAll(): Promise<string[]> {
return this.catsService.findAll();
}
}

CatsService注入到CatsController中,通过constructor (private catsService: CatsService)方法注入。

service自定义名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';

@Module({
controllers: [UserController],
providers: [UserService,{
/*
这里的名称可以随便改但是需要在 controller 中 配合 @Inject('customService') 使用!
provide: customService,
@Inject('customService')
*/
provide: 'customService',
useClass: UserService
}],
})
export class UserModule {}

1
2
3
4
@Controller('user')
export class UserController {
constructor( @Inject('customService') private readonly userService: UserService) {}
}

service工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';


@Module({
controllers: [UserController],
providers: [
{
provide: 'userServiceFactory',
useFactory: (connection: Connection) => {
return connection.getCustomRepository(UserRepository);
},
inject: [Connection],
},
UserService,
],
})
export class UserModule {}
1
2
3
4
5
6
@Controller('user')
export class UserController {
constructor(
@Inject('userServiceFactory') private readonly userService: UserService,
) {}
}

模块

  1. NestJS 中,@Module 装饰器用于组织应用程序的结构。它允许你将相关的组件(如控制器(controller)提供者(service)中间件等)组合在一起,形成一个模块。每个 NestJS 应用程序至少有一个模块,即根模块(通常是 AppModule),它引导整个应用程序

  2. 模块是组织代码和定义应用程序边界的一种方式。通过模块,你可以将应用程序分解为更小更易于管理的部分,这有助于保持代码的清晰和可维护性

创建order模块(含CRUD)操作

1
nest g res order # nest g resourse order

创建order完成后,在order.module.ts中自动引入order.controller.tsorder.service.ts!

共享模块(module)

在其它模块(app.controller.ts)下,引入order.module.ts!
exports: [OrderService] 共享模块时, 需要先导出模块!

1
2
3
4
5
6
7
8
9
10
11
import { Module } from '@nestjs/common';
import { OrderService } from './order.service';
import { OrderController } from './order.controller';

@Module({
controllers: [OrderController],
providers: [OrderService],
exports: [OrderService],
})
export class OrderModule {}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Controller, Get } from '@nestjs/common';
import { OrderService } from './order.service';

@Controller('order')
export class OrderController {
constructor(private readonly orderService: OrderService) {

}

@Get('/all')
async getAllOrders(): Promise<any> {
return 'get order all!';
}
}
1
2
3
4
5
6
7
8
9
10
import { Injectable } from '@nestjs/common';

@Injectable()
export class OrderService {
constructor() {}

getOrderAll(): string{
return "Service This is order all!!!";
}
}
1
2
3
4
5
@Module({
controllers: [OrderController],
providers: [OrderService],
exports: [OrderService],
})

其中exports,向外暴露了OrderService,这样其他模块就可以使用OrderService了。

app.controller.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Controller, Get, Query, Render } from '@nestjs/common';
import { AppService } from './app.service';
import { OrderService } from './order/order.service';

@Controller("/hello")
export class AppController {
constructor(
private readonly appService: AppService,
private readonly orderService: OrderService
) {}
/*
调用 orderService 中的 getOrderAll 方法
*/
@Get("/order")
getOrder() : string {
return this.orderService.getOrderAll();
}
}

全局模块

接下来会使用@Global()装饰器来定义全局模块!

创建config.module.ts

在根目录中config文件夹下创建config.module.ts文件!

config.module.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
import { Global, Module } from "@nestjs/common";

@Global()
@Module({
providers: [
// add providers here
{
provide: "CONFIG",
useValue: {
// add config here
baseUrl: '/api'
},
},
],
exports: [
{
provide: "CONFIG",
useValue: {
// add config here
baseUrl: '/api'
},
},
]
})
export class ConfigModule {
constructor() {
console.log("ConfigModule created");
}
}

app.module.ts中引用

app.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ClassifyModule } from './classify/classify.module';
import { UserModule } from './user/user.module';
import { OrderModule } from './order/order.module';
import { ConfigModule } from './config/config.module';
/*
引入 ConfigModule
[`UserModule, ClassifyModule, OrderModule`]这些模块都可以直接引入,不需要再次引入ConfigModule
*/
@Module({
imports: [UserModule, ClassifyModule, OrderModule, ConfigModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

imports 引入 configModule 之后, 其他模块都可以直接使用 ConfigModule 中的 CONFIG 了!

order.controller.ts中使用

@Inject('CONFIG') private readonly config: ConfigModule 注入 ConfigModule 中的 CONFIG!

order.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
import { Controller, Get, Inject } from '@nestjs/common';
import { OrderService } from './order.service';
import { ConfigModule } from 'src/config/config.module';

@Controller('order')
export class OrderController {
constructor(
private readonly orderService: OrderService,
@Inject('CONFIG') private readonly config: ConfigModule,
) {

}

@Get('/all')
async getAllOrders(): Promise<any> {
return 'get order all!';
}

@Get('/testGlobalModule')
async tesGlobalModule() {
return this.config;
}
}

中间件middleware

NestJS 中,中间件是一个函数,它在路由处理程序之前被调用,可以用来执行一些通用的任务,比如日志记录身份验证请求处理等。中间件可以访问请求对象(Request)响应对象(Response)和应用程序请求-响应循环中的下一个中间件函数(next)

next 函数类似与vue-router中的导航守卫(beforeEach)! 当调用next函数时,会执行通过路由请求,否则会挂起请求!

通过nest --help查看NestJS的命令行选项!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌───────────────┬─────────────┬──────────────────────────────────────────────┐
│ name │ alias │ description │
│ application │ application │ Generate a new application workspace │
│ class │ cl │ Generate a new class │
│ configuration │ config │ Generate a CLI configuration file │
│ controller │ co │ Generate a controller declaration │
│ decorator │ d │ Generate a custom decorator │
│ filter │ f │ Generate a filter declaration │
│ gateway │ ga │ Generate a gateway declaration │
│ guard │ gu │ Generate a guard declaration │
│ interceptor │ itc │ Generate an interceptor declaration │
│ interface │ itf │ Generate an interface │
│ library │ lib │ Generate a new library within a monorepo │
│ middleware │ mi │ Generate a middleware declaration │
│ module │ mo │ Generate a module declaration │
│ pipe │ pi │ Generate a pipe declaration │
│ provider │ pr │ Generate a provider declaration │
│ resolver │ r │ Generate a GraphQL resolver declaration │
│ resource │ res │ Generate a new CRUD resource │
│ service │ s │ Generate a service declaration │
│ sub-app │ app │ Generate a new application within a monorepo │
└───────────────┴─────────────┴──────────────────────────────────────────────┘

局部中间件

创建logger.middleware.ts

1
nest g middleware logger # nest g mi logger

/middleware/logger.middleware.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
/*
any 类型 没有提示 需要 express 导入类型给予标注后才有提示
*/
use(req: Request, res: Response, next: () => void) {

next();
}
}

order.module.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
import { Module, NestModule } from '@nestjs/common';
import { OrderService } from './order.service';
import { OrderController } from './order.controller';
import { LoggerMiddleware } from 'src/middleware/logger.middleware';

@Module({
controllers: [OrderController],
providers: [OrderService],
exports: [OrderService],
})
export class OrderModule implements NestModule {
/*
consumer 消费者
forRoutes 匹配路由 这里匹配所有路由
forRoutes 对应的参数 (string | string[] | Function | Type<any>)
forRoutes('*') 匹配所有路由
forRoutes('order') 匹配 /order 路由
forRoutes(OrderController) 匹配 OrderController 控制器中所有的路由
forRoutes({ path: 'order', method: RequestMethod.GET }) 匹配 /order 路由的 GET 请求
forRoutes({ path: 'order', method: RequestMethod.POST }) 匹配 /order 路由的 POST 请求
*/
configure(consumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}

全局中间件

创建global.middleware.ts

1
2
3
4
5
6
import { Request, Response, NextFunction } from "express"
export const globalMiddleware = (req: Request, Response: Response, next: NextFunction) => {
console.log("Global middleware is running")
console.log(`Global middleware Request... ${req.method} ${req.url} ${req.originalUrl}`);
next()
}

main.ts 中引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
import { globalMiddleware } from './middleware/global.middleware';

const assetDir = (dir) => join(__dirname, '..', dir); // 根目录
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 全局中间件
app.use(globalMiddleware);
await app.listen(3000);
}
bootstrap();

配置第三方中间件

安装cors中间件

1
2
pnpm i cors --save
pnpm i @types/cors --save-dev

main.ts 中引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
import { globalMiddleware } from './middleware/global.middleware';
import * as cors from 'cors';


const assetDir = (dir) => join(__dirname, '..', dir); // 根目录
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 全局中间件
app.use(globalMiddleware);
// 跨域中间件
app.use(cors());
await app.listen(3000);
}

附件上传

安装插件

1
2
pnpm i multer --save
pnpm i multer @types/multer --save

multer 是一个Node.js处理 multipart/form-data 上传的中间件。它可以上传文件存储文件验证文件等。
@types/multermulter类型定义文件,可以让TypeScript 识别multer 的类型和代码块提示!。

创建upload模块

1
nest g res upload # nest g resourse upload

配置和使用

upload.module.tsupload.controller.ts 中配置和使用!

upload.module.ts

  • MulterModule.register(): 注册上传模块!
  • diskStorage(): 存储上传文件到指定目录!

upload.controller.ts

  • @Post('/album'): 上传文件路由!
  • @UploadedFile() file: 获取上传文件!
  • @UseInterceptors(FileInterceptor('file')): 上传文件中间件!

请求路径: http://localhost:3000/upload/album
请求参数: file: 文件 blob!

访问上传静态资源

1
2
3
4
5
6
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useStaticAssets(assetDir('uploadFiles'), {
prefix: '/assets/uploadFiles/',
}); // 配置静态资源目录
}

路径访问实例:: http://localhost:3000/assets/uploadFiles/1717295349937-file-9189.jpg

附件下载

直接下载

以下方式下载路径写死的,需要根据实际情况修改!

通过装饰器 @Res() resdownload方法来进行直接下载!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 
文件下载: download 方式
*/
@Get('/download')
async download(@Res() res) {
const fileUrl = join(__dirname, '../uploaFiles/1717311840261-file-9216.jpg')
try {
res.download(fileUrl);
} catch (error) {
console.log("download error", error)
return {
code: 500,
message: '下载失败',
error
}
}
}

文件流下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 
文件下载: stream 流方式
*/
@Get('/stream')
async stream(@Res() res) {
const fileUrl = join(__dirname, '../uploaFiles/1717311840261-file-9216.jpg')
try {
const readStream = require('fs').createReadStream(fileUrl)
readStream.pipe(res)
} catch (error) {
console.log("stream error", error)
return {
code: 500,
message: '下载失败',
error
}
}
}

rxjs

在 NestJS 中,RxJS 用于处理异步操作事件流。例如,当你需要处理 HTTP 请求数据库操作或任何其他异步任务时,你可以使用 RxJSObservables 来创建和管理这些异步数据流

  • Observables: 异步数据流
  • Subscription: 订阅 监听 Observables
  • Operators: 操作符
  • Subjects: 主体
  • Schedulers: 调度器

创建rxjs.ts

在 src 目录下创建 rxjs.ts 文件!

需要通过ts-node方式来执行rxjs.ts文件!

1
pnpm i ts-node -D

运行rxjs.ts

1
npx ts-node src/rxjs.ts

导入模块

1
2
import { Observable, of, from, interval, take } from 'rxjs';
import { map, filter, tap } from 'rxjs/operators';

案例一Observable

使用迭代器next发出通知,并使用complete来完成通知!

案例一
1
2
3
4
5
6
7
8
9
10
11
const source = new Observable((observer) => {
observer.next('Hello');
observer.next('World');

setTimeout(() => {
observer.next('我是异步函数!')
observer.complete();
}, 1000);
});

source.subscribe((value) => console.log(value));

案例二of

使用of()函数来创建 Observable 对象,并通过subscribe()订阅它!

1
2
3
4
5
6
7
const source2 = of('Hello', 'World');

source2.subscribe((value) => console.log(value));

// 输出:
// Hello
// World

案例三from

使用from()函数来创建 Observable 对象,并通过subscribe()订阅它!

1
2
3
4
5
6
7
8
9
10
const source3 = from([1, 2, 3, 4, 5]);

source3.subscribe((value) => console.log(value));

// 输出:
// 1
// 2
// 3
// 4
// 5

案例四interval

使用interval()函数来创建 Observable 对象,并通过subscribe()订阅它!

1
2
3
4
5
6
7
8
9
10
11
12
13
const source4 = interval(1000);
// 无限输出
// source4.subscribe((value) => console.log(value));
// 也可以加管道进行处理 只会输出到5次
source4.pipe(take(5)).subscribe((value) => console.log(value));

// 输出:
// 0
// 1
// 2
// 3
// 4
// 5

案例五map

使用map()操作符来映射 Observable 对象,并通过subscribe()订阅它!

1
2
3
const source5 = of('Hello', 'World').pipe(map((value) => value.toUpperCase()));

source5.subscribe((value) => console.log(value));

案例六info

使用filter()操作符来过滤 Observable 对象,并通过subscribe()订阅它!

1
2
3
4
const source6 = of('Hello', 'World', '你好', '안녕하세요', 'こんにちは', '안녕하세요').pipe(filter((value) => value.length > 2));


source6.subscribe((value) => console.log(value));

案例七tap

使用tap()操作符来拦截 Observable 对象,并通过subscribe()订阅它!

1
2
3
const source7 = of('Hello', 'World').pipe(tap((value) => console.log(`before: ${value}`)));

source7.subscribe((value) => console.log(`after: ${value}`));

取消订阅

通过subscription.unsubscribe() 来取消监听机制!

1
2
3
4
5
6
7
8
9
let subscription = interval(1000).pipe(
map((value) => { return { num : value }})
).subscribe(e => {
console.log("e: ", e)
if ( e.num === 5 ) {
// 取消监听和订阅
subscription.unsubscribe();
}
});

以上代码会在5秒后取消监听和订阅!

响应拦截器

Nest框架中,拦截器(Interceptors)是一种强大的机制,它允许你在请求处理流程中的某个点上拦截和修改请求响应。拦截器可以用于日志记录错误处理权限验证数据转换等多种场景。

要创建一个响应拦截器,你需要实现NestInterceptor接口。这个接口要求你实现一个intercept方法,该方法接收两个参数:ExecutionContextCallHandler

创建response.interceptor.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Observable, map } from "rxjs";

@Injectable()
export class ResponseInterceptor<T> implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
console.log("Response interceptor", next);
return next.handle().pipe(map(data => ({
data: data,
message: "请求成功!",
success: true
})));
}
}

在这个例子中,ResponseInterceptor会在请求处理之后处理响应结果intercept方法调用next.handle()来继续请求处理流程next.handle()返回一个Observable,你可以在这个Observable上使用RxJS操作符来修改响应。在这个例子中,我们使用了map操作符来定义响应结果!

全局拦截器

main.ts中引入ResponseInterceptor,通过app.useGlobalInterceptors()方法注册全局拦截器!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import { ResponseInterceptor } from './interceptor/response';
const assetDir = (dir) => join(__dirname, dir); // 根目录
/*
// 访问 "public"下的 image[静态资源]
需要改成 这个目录 join(__dirname, '..', dir);
*/
const dirName = "uploaFiles" // "public"
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 全局拦截器
app.useGlobalInterceptors(new ResponseInterceptor());

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

请求路径: http://localhost:3000/user

1
2
3
4
5
6
7
8
// 20240603172915
// http://localhost:3000/user

{
"data": "This action returns all user",
"message": "请求成功!",
"success": true
}

局部拦截器

控制器 [controller]中引入ResponseInterceptor,通过@UseInterceptors()方法注册局部拦截器!

1
2
3
4
5
6
7
8
9
10
11
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { ResponseInterceptor } from './response.interceptor';

@Controller('cats')
@UseInterceptors(ResponseInterceptor)
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}

异常拦截器

NestJS中,异常过滤器(Exception Filters)是一种特殊类型的中间件,用于处理应用程序中抛出的异常。它们允许你为应用程序中的不同异常类型定义统一的处理逻辑,从而提供更加健壮和用户友好的错误处理机制

要创建一个异常过滤器,你需要实现ExceptionFilter接口。这个接口要求你实现一个catch方法,该方法接收两个参数:exceptionhost

创建http-exception.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
28
29
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from "@nestjs/common";
import { Request, Response } from "express";

@Catch(HttpException)
export class HttpErrorFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
// 获取 http 上下文
const ctx = host.switchToHttp();
// 响应上下文
const response = ctx.getResponse<Response>();
// 请求上下文
const request = ctx.getRequest<Request>();
// 状态码
const status = exception.getStatus();
// 错误消息
const message = exception.message;
// error 信息
const error = exception.getResponse();
// 响应错误信息
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
success: false,
error
});
}
}

全局异常拦截器

main.ts中引入HttpErrorFilter,通过app.useGlobalFilters()方法注册全局异常拦截器!

1
2
3
4
5
6
7
8
9
10
11
12
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import { HttpErrorFilter } from './interceptor/http-exception.filter.ts';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 全局异常拦截器
app.useGlobalFilters(new HttpErrorFilter());

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

请求路径: http://localhost:3000/aaa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 20240603174928
// http://localhost:3000/aaa

{
"statusCode": 404,
"timestamp": "2024-06-03T09:49:28.399Z",
"path": "/aaa",
"message": "Cannot GET /aaa",
"success": false,
"error": {
"message": "Cannot GET /aaa",
"error": "Not Found",
"statusCode": 404
}
}

局部异常拦截器

控制器 [controller]中引入HttpErrorFilter,通过@UseFilters()方法注册局部异常拦截器!

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

@Controller('cats')
@UseFilters(HttpErrorFilter)
export class CatsController {
@Get()
findAll(): string {
throw new HttpException('This action returns all cats', 404);
}
}

管道

数据转换

可以将前端传入的参数类型进行格式转换!

1
2
3
4
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.userService.findOne(+id);
}

ParseIntPipe 用于将参数转换为number类型!
除了 ParseIntPipe 以外还有其他类型转换!

  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • ParseEnumPipe
  • ParseDatePipe
  • ParseJsonPipe
  • ParseFloatPipe
  • DefaultValuePipe
  • ValidationPipe

局部数据验证

类型检查、数据格式检查、数据长度检查、数据范围检查等!
需要安装class-validatorclass-transformer来进行格式校验!

  • class-validator: 用于数据类型校验!
  • class-transformer: 用于数据转换将接受的参数映射到实体类!

安装插件

1
pnpm i class-validator class-transformer -D

创建login模块

1
nest g res login # nest g resource login

创建login自定义管道

自定义管道可以对请求的参数进行校验和处理!

1
nest g pi login/login-validation # nest g pipe login/login-validation

编辑create.login.dto.ts

dto实体类中加入class-validator的装饰器!

  • IsNotEmpty: 校验是否为空!
  • IsNumber: 校验是否为number类型!
  • IsString: 校验是否为string类型!
  • Length: 校验字符串长度!
1
2
3
4
5
6
7
8
9
10
11
12
import { IsNotEmpty, IsNumber, IsString, Length } from 'class-validator';

export class CreateLoginDto {
@IsString()
@IsNotEmpty()
@Length(5,10,{
message: "用户名长度必须在5到10个字符之间!"
})
username: string;
@IsString()
password: string;
}

编辑login.pipe.ts

login.pipe.ts文件为自定义管道,其中transform方法用于对请求的参数进行校验和处理
其中value是请求的参数,metadata是请求的元数据
metadata元数据中包含: { metatype: [class CreateLoginDto], type: 'body', data: undefined }

  1. 请求类型(type)
  2. 请求实体dto(metatype)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { ArgumentMetadata, HttpException, HttpStatus, Injectable, PipeTransform } from '@nestjs/common';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';

@Injectable()
export class LoginPipe implements PipeTransform {
async transform(value: any, metadata: ArgumentMetadata) {
console.log('LoginPipe', value, metadata);
// 映射参数到实体
const dto = plainToInstance(metadata.metatype, value);
console.log("dto", dto)
// 校验dto
const validateResult = await validate(dto);
console.log("validateResult", validateResult)
if ( validateResult.length ) {
throw new HttpException(
{ errors: validateResult, message: 'Validation failed params is not valid pass!' },
HttpStatus.BAD_REQUEST
);
}
return value;
}
}

通过class-transformer中的plainToInstance方法进行参数实体之间数据映射!
通过class-validator中的validate方法进行dto实体的数据校验!

编辑login.controller.ts

controller中引入pipe自定义管道,并放在参数装饰器中@Body(LoginPipe)!

1
2
3
4
@Post()
create(@Body(LoginPipe) createLoginDto: CreateLoginDto) {
return this.loginService.create(createLoginDto);
}

校验结果
请求路径: http://localhost:3000/login
请求参数:

1
2
3
4
{
"username": "张三",
"password": 123456
}

validateResult
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
{
"statusCode": 400,
"timestamp": "2024-06-04T02:33:54.469Z",
"path": "/login",
"message": "Validation failed params is not valid pass!",
"success": false,
"error": {
"errors": [
{
"target": {
"username": "张三",
"password": 123456
},
"value": "张三",
"property": "username",
"children": [],
"constraints": {
"isLength": "用户名长度必须在5到10个字符之间!"
}
},
{
"target": {
"username": "张三",
"password": 123456
},
"value": 123456,
"property": "password",
"children": [],
"constraints": {
"isString": "password must be a string"
}
}
],
"message": "Validation failed params is not valid pass!"
}
}

全局数据验证

在入口文件main.ts中引入ValidationPipe

1
2
3
4
5
6
7
8
9
10
11
12
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 全局参数校验管道
app.useGlobalPipes(new ValidationPipe())
await app.listen(3000);
}
bootstrap();

校验结果

validateResult
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"statusCode": 400,
"timestamp": "2024-06-04T02:57:45.992Z",
"path": "/login",
"message": "Bad Request Exception",
"success": false,
"error": {
"message": [
"用户名长度必须在5到10个字符之间!",
"password must be a string"
],
"error": "Bad Request",
"statusCode": 400
}
}

守卫

NestJS 中,守卫(Guards)是用于控制对特定路由的访问的中间件。它们在路由处理程序执行之前运行,并且可以访问请求对象(Request)、响应对象(Response)以及控制器的上下文(Context)。守卫可以执行各种任务,比如验证用户身份检查权限管理请求的生命周期等。

  1. 执行访问控制逻辑:根据用户的角色权限其他条件决定是否允许访问某个路由
  2. 修改请求对象:在处理程序执行之前,可以对请求对象进行修改
  3. 决定是否继续执行请求:守卫可以决定请求是否应该继续传递给下一个中间件或处理程序。

要创建一个守卫,你需要实现 CanActivate 接口,该接口有一个 canActivate 方法。这个方法应该返回一个布尔值表示是否允许请求继续执行

创建守卫

1
nest g guard role

role.guard.ts

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

@Injectable()
export class RoleGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
console.log("RoleGuard",request.user);
return true;
}
}

局部守卫

user.controller.ts中引入守卫

局部守卫
1
2
3
4
5
6
7
8
9
10
11
12
import { Controller, Get, UseGuards } from '@nestjs/common';
import { RoleGuard } from 'src/role/role.guard';

@Controller('user')
@UseGuards(RoleGuard)
export class UserController {
@Get()
@SetMetadata('roles', ['admin'])
findAll() {
return this.userService.findAll();
}
}

UseGuards 装饰器来引入守卫。
@SetMetadata('role', ['admin']) 装饰器来设置角色信息,并将meta data传送至role.guard.ts守卫中通过Reflector来获取。

role.guard.ts中获取角色信息

role.guard.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { Observable } from 'rxjs';

@Injectable()
export class RoleGuard implements CanActivate {
// 注入 Reflector
constructor(private reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest<Request>();
const roles = this.reflector.get<string[]>('roles', context.getHandler());
console.log("RoleGuard",roles); // ['admin']
return true;
}
}

全局守卫

main.ts中引入role.guard.ts, 并通过app.useGlobalGuards()方法注册全局守卫!

1
app.useGlobalGuards(new RoleGuard())

自定义装饰器

创建自定义装饰器

1
nest g decorator auth # nest g d auth

自定义装饰器
1
2
3
import { SetMetadata } from '@nestjs/common';

export const Role = (...args: string[]) => SetMetadata('role', args);

user.controller.ts使用

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Controller, Get, UseGuards } from '@nestjs/common';
import { RoleGuard } from 'src/role/role.guard';

@Controller('user')
@UseGuards(RoleGuard)
export class UserController {
@Get()
@Role('admin')
// @SetMetadata('roles', ['admin'])
findAll() {
return this.userService.findAll();
}
}

swagger文档

Swagger 允许你描述 API 的结构,以便机器可以读取它们。这意味着你可以自动生成文档客户端库等。
在 NestJS 中使用 Swagger,你可以通过安装 @nestjs/swagger 包来实现。以下是使用 Swagger 的基本步骤:

  1. 安装 @nestjs/swagger 包:
    1
    npm install @nestjs/swagger swagger-ui-express @types/swagger-schema-official -D
    @types/swagger-schema-official 类型声明,有了这个才会有代码提示!
  2. 在你的NestJS 应用中导入 SwaggerModule 并配置它:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import { NestFactory } from '@nestjs/core';
    import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
    import { AppModule } from './app.module';

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

    // 设置 Swagger 文档
    const options = new DocumentBuilder()
    .setTitle('NestJS API')
    .setDescription('The NestJS API description')
    .setVersion('1.0')
    .addTag('api')
    .build();

    const document = SwaggerModule.createDocument(app, options);
    SwaggerModule.setup('api-docs', app, document);

    await app.listen(3000);
    }
    bootstrap();
  3. 在你的控制器中使用装饰器来描述路由和参数:
    controller
    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
    import { Controller, Get, Query, Body, Param, Description } from '@nestjs/common';
    import { ApiTags, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';

    @Controller('cats')
    @ApiTags('cats')
    export class CatsController {
    @Get()
    @ApiOperation({ summary: '获取所有猫的信息' })
    findAll(): string {
    return 'This action returns all cats';
    }

    @Get(':id')
    @ApiOperation({ summary: '获取一只猫的信息' })
    findOne(@Param('id') id: string): string {
    return `This action returns a #${id} cat`;
    }

    @Post()
    @ApiOperation({ summary: '创建一只猫' })
    create(@Body() body): string {
    return body;
    }

    @Delete(':id')
    @ApiOperation({ summary: '删除一只猫' })
    remove(@Param('id') id: string): string {
    return `This action removes a #${id} cat`;
    }
    }
  4. 访问 Swagger UI
    http://localhost:3000/api-docs

swagger中常用的装饰器

@ApiTags

用于给控制器或方法添加标签,以便在 Swagger UI 中对 API 进行分组分类

1
2
3
4
5
@ApiTags('users')
@Controller('users')
export class UsersController {
// ...
}

@ApiOperation

用于描述控制器方法的详细信息,包括摘要描述响应等。。

1
2
3
4
5
@ApiOperation({ summary: '获取用户信息', description: '根据用户ID获取用户信息' })
@Get(':id')
async getUserById(@Param('id') id: string) {
// ...
}

@ApiParam

用于描述控制器方法的参数,包括参数名称类型是否必须等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ApiTags('users')
@Controller('users')
export class UsersController {
@Get()
@ApiOperation({ summary: '获取用户列表' })
@ApiParam({ name: 'limit', required: false, description: '限制返回的用户数量', example: 10 })
@ApiParam({ name: 'offset', required: false, description: '偏移量', example: 0 })
async getUsers(
@Param('limit', new ParseIntPipe()) limit: number,
@Param('offset') offset: number = 0
) {
// ...
}
}

@ApiQuery

用于描述控制器方法的查询参数,通常与 @Query() 装饰器一起使用。

1
2
3
4
5
6
7
8
9
10
@Get()
@ApiOperation({ summary: '获取用户列表' })
@ApiQuery({ name: 'limit', required: false, description: '限制返回的用户数量', example: 10 })
@ApiQuery({ name: 'offset', required: false, description: '偏移量', example: 0 })
async getUsers(
@Query('limit', new ParseIntPipe()) limit: number,
@Query('offset') offset: number = 0
) {
// ...
}

@ApiBody

用于描述控制器方法的请求体,通常与 @Body() 装饰器一起使用。

1
2
3
4
5
6
7
8
@Post()
@ApiOperation({ summary: '创建新用户' })
async createUser(
@ApiBody({ description: '用户信息' })
@Body() createUserDto: CreateUserDto
) {
// ...
}

@ApiProperty

用于描述实体类的属性,包括名称类型描述是否必填等。

1
2
3
4
5
6
7
export class CreateUserDto {
@ApiProperty({ description: '用户名', example: 'john' })
username: string;

@ApiProperty({ description: '密码', example: '123456' })
password: string;
}

@ApiResponse

用于描述控制器方法的响应,包括状态码描述模式

1
2
3
4
5
6
@Post()
@ApiOperation({ summary: '创建新用户' })
@ApiResponse({ status: 201, description: '用户创建成功' })
async createUser(@Body() createUserDto: CreateUserDto) {
// ...
}

@ApiOkResponse

用于描述控制器方法的正常响应(通常是 HTTP 200 状态码)。

1
2
3
4
5
6
@Get(':id')
@ApiOperation({ summary: '获取用户信息' })
@ApiOkResponse({ description: '用户信息', type: User })
async getUserById(@Param('id') id: string) {
// ...
}

@ApiBadRequestResponse

用于描述控制器方法的错误请求响应(通常是 HTTP 400 状态码)。

1
2
3
4
5
6
@Post()
@ApiOperation({ summary: '创建新用户' })
@ApiBadRequestResponse({ description: '无效的请求体' })
async createUser(@Body() createUserDto: CreateUserDto) {
// ...
}

@ApiUnauthorizedResponse

用于描述控制器方法的未授权响应(通常是 HTTP 401 状态码)。

1
2
3
4
5
6
@Get()
@ApiOperation({ summary: '获取用户列表' })
@ApiUnauthorizedResponse({ description: '未授权访问' })
async getUsers() {
// ...
}

@ApiForbiddenResponse

用于描述控制器方法的禁止访问响应(通常是 HTTP 403 状态码)。

1
2
3
4
5
6
@Get()
@ApiOperation({ summary: '获取用户列表' })
@ApiForbiddenResponse({ description: '禁止访问' })
async getUsers() {
// ...
}

@ApiNotFoundResponse

用于描述控制器方法的未找到响应(通常是 HTTP 404 状态码)。

1
2
3
4
5
6
@Get(':id')
@ApiOperation({ summary: '获取用户信息' })
@ApiNotFoundResponse({ description: '用户不存在' })
async getUserById(@Param('id') id: string) {
// ...
}

@ApiConflictResponse

用于描述控制器方法的冲突响应(通常是 HTTP 409 状态码)。

1
2
3
4
5
6
@Post()
@ApiOperation({ summary: '创建新用户' })
@ApiConflictResponse({ description: '用户名已存在' })
async createUser(@Body() createUserDto: CreateUserDto) {
// ...
}

swagger配置token

为了在 Swagger UI 中显示 token 认证,需要在 app.module.ts 中配置 SwaggerModule

swaggerToken
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';


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

const options = new DocumentBuilder()
.setTitle('NestJS API')
.setDescription('The NestJS API description')
.setVersion('1.0')
.addBearerAuth()
.build();

const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api-docs', app, document);

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

addBearerAuth() 方法用于添加 Bearer 认证,setBearerAuth() 方法用于设置认证名称和位置。

typeorm数据库映射

TypeORM,这是一个强大的ORM(对象关系映射)库,用于Node.js,支持多种数据库

安装依赖

1
pnpm install @nestjs/typeorm typeorm mysql2 --save

配置TypeORM

app.module.ts中加入typeorm配置项!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
imports: [
TypeOrmModule.forRoot({
type:'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
// entities: [__dirname + '/**/*.entity{.ts,.js}'], // autoLoadEntities 为 true 时 无需指定 entities
synchronize: true,
autoLoadEntities: true
}),
],
})
export class AppModule {}
配置项说明类型
type数据库类型mysql oracle sqlite
host数据库主机地址string
port数据库主机端口number
username数据库用户名string
password数据库密码string
database数据库名称string
entities实体文件路径string[]
synchronize是否自动将实体类同步到数据库字段boolean
autoLoadEntities是否自动加载实体类boolean
retryAttempts连接失败重试次数number
retryDelay连接失败重试延迟number

配置实体类

user.entity.ts

定义User实体类,用于映射User表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: string;
@Column()
username: string;
@Column()
password: string;
@Column()
tel: string;
@Column()
email: string;
@Column()
address: string;
}
装饰器描述
@Entity()定义实体类
@PrimaryGeneratedColumn()定义主键,并设置自增
@Column()定义字段,并设置类型

注册实体类模块

user.modules.ts模块中通过TypeOrmModule.forFeature([User]) 来进行注册!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { User } from './entities/user.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
/*
导入 user 实体模块
*/
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService,{
/*
这里的名称可以随便改但是需要在 controller 中 配合 @Inject('customService') 使用!
provide: customService,
@Inject('customService')
*/
provide: 'customService',
useClass: UserService
}],
})
export class UserModule {}

TypeOrm装饰器

Entity()

用于定义实体类,并设置表名。

1
2
3
4
@Entity({ name: 'users' })
export class User {
// ...
}

PrimaryGeneratedColumn()

用于定义主键,并设置自增。

1
2
3
4
5
6
@PrimaryGeneratedColumn()
id: string;

// 或者
@PrimaryGeneratedColumn('uuid')
id: string;

Column()

用于定义字段,并设置类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Column({ type: 'varchar', length: 50 })
username: string;

@Column('simple-array')
names: string[];

@Column({ type: 'enum', enum: ['male', 'female'] })
gender: string;

@Column('simple-array')
// @Column({ type: 'json' })
meta: object;

@Column({ type: 'decimal', precision: 10, scale: 2 })
price: number;

Column({})配置项

配置项说明类型
type字段类型string number boolean date json enum
length字段长度number
precision字段精度number
scale字段小数位数number
default字段默认值string number boolean date json enum
nullable是否可为空boolean
unique是否唯一boolean
comment字段注释string
enum枚举类型any[]
generated是否生成string uuid increment
name字段名称string
update是否更新boolean
primary是否主键boolean
unique是否唯一boolean
select是否查询(为true时,返回结果将会过滤该字段)boolean
insert是否插入boolean
update是否更新boolean
delete是否删除boolean
default默认值string number boolean date json enum

Index()

用于定义索引。

1
@Index('idx_user_username', ['username'], { unique: true })

Generated()

用于定义生成器。

1
2
Generated('uuid')
uuid: string;

CreateDateColumn()

用于定义创建时间字段。

1
2
@CreateDateColumn()
created_at: Date;

UpdateDateColumn()

用于定义更新时间字段。

1
2
@UpdateDateColumn()
updated_at: Date;

ManyToOne()

用于定义一对多关系。

1
2
@ManyToOne(() => User, (user) => user.posts)
user: User;

OneToMany()

用于定义一对多关系。

1
2
@OneToMany(() => Post, (post) => post.user)
posts: Post[];

ManyToMany()

用于定义多对多关系。

1
2
@ManyToMany(() => Tag, (tag) => tag.posts)
tags: Tag[];

JoinTable()

用于定义多对多关系的中间表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ManyToMany(() => Tag, {
joinTable: {
name: 'post_tag',
joinColumn: {
name: 'post_id',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'tag_id',
referencedColumnName: 'id',
},
},
})
tags: Tag[];