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 catscats.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.tsmodule 模块
模块是具有 @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 验证入参
守卫
守卫有一个单独的责任。它们根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理。
守卫在每个中间件之后执行,但在任何拦截器或管道之前执行。
