nestjs基础
nestjs 基础
Controllers 控制层
Controller
业务控制,就是控制业务层 Service
的,它的作用主要是架起了外界与业务层沟通的桥梁,移动端、前端在调用接口访问相关业务时,都会通过 Controller
,由 Controller
去调相关的业务层代码并把数据返回给移动端和前端。
路由
控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作。
使用 @Controller()
装饰器定义一个基本的控制器,路由路径前缀设置为 cats;在请求方法的装饰器中设置所需要的路由路径。
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
// @Controller('cats') 和 @Get('findAll') 就组成了接口 GET /cats/findAll
@Get('findAll')
async findAll(): Promise<ResultData> {
return '返回的数据';
}
}
状态码
如上所述,默认情况下,响应的状态码总是默认为 200,除了 POST 请求(默认响应状态码为 201)
可以通过在处理函数外添加 @HttpCode()
装饰器来修改状态码
@Post()
@HttpCode(204)
create() {
return '创建成功'
}
Headers 响应头
要指定自定义响应头,可以使用 @header()
装饰器
@Post()
@Header('Cache-Control', 'none')
create() {
return '创建成功'
}
重定向
要将响应重定向到特定的 URL
,可以使用 @Redirect()
装饰器
@Post()
@Redirect('https://nestjs.com', 301)
数据传参
路由参数,例如,使用 GET /cats/1
来获取 id 为 1 的 cat
@Get(':id')
findCat(@Param() params): string {
console.log(params.id);
return `获取id为${params.id}的cat`;
}
GET 请求 query 传参
@Get('findCat')
findCat(@Query() query) {
return `获取id为${query.id}的cat`;
}
POST 请求 body 传参
@Post('createCat')
async create(@Body() createCatDto) {
return `新增的猫猫是${createCatDto.name}`;
}
请求负载
需要确定 DTO
(数据传输对象)模式。DTO 是一个对象,它定义了如何通过网络发送数据。我们可以通过使用 TypeScript 接口(Interface)或简单的类(Class)来定义 DTO 模式。
dto/cat.interface.ts
export class CreateCatDto {
readonly name: string;
readonly age: string;
readonly breed: string;
}
之后,就可以在 CatsController 中使用新创建的 DTO
cats.service.ts
import { CreateCatDto } from './dto/create-cat.dto';
Providers 提供者
Providers
是 Nest
的一个基本概念。许多基本的 Nest 类都可能被视为 provider;service
, repository
, factory
, helper
等等。 他们都可以通过 constructor 注入依赖关系。
Service 业务层
业务层,所有的内部的业务逻辑都会放在这里处理,比如用户的增删改查,或者发送个验证码或邮件,或者做一个抽奖活动等等等等,都会在 Service
中进行。
可以通过 CLI 创建服务类,在项目更目录下执行命令,会在 src 目录下生成对应的文件夹及 service 文件
nest g service cats
cats.service.ts
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsServics {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}
service 中用到了一个Cat
接口
interfaces/cat.interface.ts
export interface Cat {
name: string;
age: string;
breed: string;
}
依赖注入
service
通过类构造函数注入的
import { Body, Controller, Get, Post } from '@nestjs/common';
import { CatsServics } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsServics) {}
@Post('createCat')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
return '创建成功';
}
@Get('findAll')
async findAll() {
return this.catsService.findAll();
}
}
注册提供者
现在我们已经定义了提供者(service),并且已经有了该服务的使用者(controller),我们需要在 Nest 中注册该服务,以便它可以执行注入。
app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './system/cats/cats.controller';
import { CatsServics } from './system/cats/cats.service';
@Module({
providers: [CatsServics],
controllers: [CatsController],
})
export class AppModule {}
此时的项目机结构是
src
├── system
│ └── cats
│ ├── dto
│ │ └── create-cat.dto.ts
│ ├── interfaces
│ │ └── cat.interface.ts
│ ├── cats.service.ts
│ └── cats.controller.ts
├── app.module.ts
└── main.ts
module 模块
模块是具有 @Module() 装饰器的类。
功能模块
将同属一个应用程序域的 controller 和 service 整合到一个功能模块下
cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsServics } from './cats.service';
@Module({
providers: [CatsServics],
controllers: [CatsController],
})
export class CatsModule {}
然后将应用模块注入到根模块
app.module.ts
import { Module } from '@nestjs/common';
import { CatsModule } from './system/cats/cats.module';
@Module({
imports: [CatsModule],
controllers: [],
})
export class AppModule {}
模块导出
模块可以导出他们的内部提供者。 而且,他们可以再导出自己导入的模块。
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class CoreModule {}
全局模块
如果你不得不在任何地方导入相同的模块,提供者是在全局范围内注册的。
import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
中间件
中间件函数可以执行以下任务:
- 执行任何代码。
- 对请求和响应对象进行更改。
- 结束请求-响应周期。
- 调用堆栈中的下一个中间件函数。
- 如果当前的中间件函数没有结束请求-响应周期, 它必须调用 next() 将控制传递给下一个中间件函数。否则, 请求将被挂起。
应用中间件
在函数中或在具有 @Injectable()
装饰器的类中实现自定义 Nest 中间件。
logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: any, res: any, next: (error?: any) => void) {
console.log(`request...`);
next();
}
}
中间件不能在 @Module() 装饰器中列出。我们必须使用模块类的 configure() 方法来设置它们。 将包含路由路径的对象和请求方法传递给 forRoutes()方法,从而进一步将中间件限制为特定的请求方法。
app.module.ts
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { CatsModule } from './system/cats/cats.module';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}
异常过滤器
内置的异常层负责处理整个应用程序中的所有抛出的异常。当捕获到未处理的异常时,最终用户将收到友好的响应。
基础异常类
Nest
提供了一个内置的 HttpException
类。
cats.controller.ts
import { HttpStatus } from '@nestjs/common';
@Get('forbidden')
async forbidden() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
自定义异常
如果确实需要创建自定义的异常,则最好创建自己的异常层次结构。
forbidden.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
cats.controller.ts
import { ForbiddenException } from './forbidden.exception';
@Get('forbidden')
async forbidden() {
throw new ForbiddenException();
}
捕获异常
为了捕获每一个未处理的异常,将 @Catch() 装饰器的参数列表设为空。
http.filter.ts
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
HttpStatus,
} from '@nestjs/common';
@Catch()
export class XHttpFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
将异常过滤器拿到全局使用
main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { XHttpFilter } from './common/http.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new XHttpFilter());
await app.listen(3000);
}
bootstrap();
管道
在 Nest 的执行顺序中,管道的所处的位置是 middleware
之后在 router(controller 中的方法)
之前。其作用总的来说只有两个:验证输入的数据是否合法以及转换参数(一般都是字符串类型)到合适的类型。
内置管道
nest 内置了 9 个管道
类名 | 作用 |
---|---|
ValidationPipe | 验证参数有效性 |
ParseIntPipe | 转换为整形 |
ParseBoolPipe | 转换为布尔形 |
ParseFloatPipe | 转换为浮点形 |
ParseArrayPipe | 转换为数组 |
ParseUUIDPipe | 转换为 UUID |
ParseEnumPipe | 转换为枚举 |
ParseFilePipe | 文件 |
DefaultValuePipe | 验证参数有效性 |
绑定管道
将管道与特定的路由处理程序方法相关联,并确保它在该方法被调用之前运行。
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
基于 DTO 的验证
详情可见DTO 验证入参
守卫
守卫有一个单独的责任。它们根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理。
守卫在每个中间件之后执行,但在任何拦截器或管道之前执行。