跳到主要内容

5.2-主要概念

Create by fall on 14 Dec 2021 Recently revised in 22 Aug 2024

Controller

控制器

控制器处理传入的请求和想客户端返回的响应,

路由机制控制那个控制器处理那个请求。通常每个控制器有多个路由,不同的路由进行不同的操作。

/* cats.controller.ts */
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get('/around') // 用于告诉 Nest 为 HTTP 请求的特定端点创建处理程序
findAll(): string {
return 'This action returns all cats';
}
}

路由路径 ?一个处理程序的路由路径是通过连接为控制器 (Controller) 声明的(可选)前缀和请求装饰器中指定的任何路径来确定的。

Provider

Controller 会调用 provider 中的函数,

provider 包括 service

// 创建 code.service.ts
import { Injectable } from '@nestjs/common'
@Injectable()
export class CodeService {
findAll () {
return [{
name: '代码片段1',
content: 'const a = 106',
}]
}
}

调用 service

// code.controller.ts
import { CodeService } from './code.service'
@Controller('code')
export class CodeController {
//
constructor (private readonly codeService: CodeService) {}
@Get()
findAll () {
return this.codeService.findAll()
}
}

Modules


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 {}

全局 module 应该只被注册一次

dynamic modules

import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';

@Module({
providers: [Connection],
exports: [Connection],
})
export class DatabaseModule {
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}

Middleware

中间件,可以做哪些内容?

  • 在特定请求中,执行任意代码
  • 对返回内容进行统一包装
  • 终止 request-response 循环
  • 进入下一个中间件
  • 中间件如果没有结束 request-response循环,必须调用 next(),否则将会一直挂起

import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
imports: [CatsModule],
})
// 配置中间件
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
// 如果要使用多个中间件可以
// .apply(cors(), helmet(), logger)
.exclude( // 排除一些路由
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)',
)
.forRoutes({ path: 'cats', method: RequestMethod.GET });
// 还可以绑定 Controller
// .forRoutes(CatsController)
}
}

函数式写法

如果你的中间件没有任何依赖,请使用函数式中间件来代替

import { Request, Response, NextFunction } from 'express';

export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
};

意外捕获

https://docs.nestjs.com/exception-filters

默认就有一个全局意外捕获

{
"message": "Cannot POST /faker",
"error": "Not Found",
"statusCode": 404
}

自定义全局捕获

@Get()
async findAll() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

管道

pipe

https://docs.nestjs.com/pipes

  • 将数据转换为想要的类型
  • 验证,验证数据是否有效

当在 pipe 中抛出了一个错误,之后的 controller 将不会执行

nest 提供的 pipe line

  • ValidationPipe
  • ParseIntPipe
  • ParseFloatPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe,验证 ParseUUIDPipe() 你在使用 UUID 3, 4 or 5 的版本
  • ParseEnumPipe
  • DefaultValuePipe
  • ParseFilePipe

使用

@Controller('code')
export class CodeController {
constructor (private readonly codeService: CodeService) {}

@Get(':id')
findOne (@Param('id', new ParseUUIDPipe()) id: string) {
// request
console.log('🚀 ~ CodeController ~ findOne ~ request:', randomUUID())
return this.codeService.findOne(id)
}
}
@Controller('code')
export class CodeController {
constructor (private readonly codeService: CodeService) {}@Post()

@HttpCode(200)
create (@Body('codeType', ParseTypePipe) createCodeDto: CreateCodeDto) {
console.log('🚀 ~ CodeController', createCodeDto)
return this.codeService.create(createCodeDto)
}
}

为什么不是中间件?

中间件确实可以工作,但是中间件并不知道你调用了哪个参数,使用了哪个方法因此很难创造出通用的中间件

增强 dto

@Controller('code')
export class CodeController {
constructor (private readonly codeService: CodeService) {}

@Patch(':id')
@UseInterceptors(NoFilesInterceptor()) // 接收 FormData
update (@Param('id') id: string, @Body(new ValidationPipe()) updateCodeDto: UpdateCodeDto) {
console.log('🚀 ~ CodeController ~ update ~ param:', updateCodeDto)
return this.codeService.update(id, updateCodeDto)
}
}

守卫

guards

定义一个守卫:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
import { Observable } from 'rxjs'
@Injectable()
export class RulesGuard implements CanActivate {
canActivate (
context:ExecutionContext
):boolean|Promise<boolean>|Observable<boolean> {
const request = context.switchToHttp().getRequest()
console.log('🚀 ~ AuthGuard ~ request:', request)
// validateRequest(request)
return true
}
}

使用一个守卫

import { Controller, UseGuards } from '@nestjs/common'

@Controller('code')
@UseGuards(RulesGuard)
export class CodeController {}

// 也可以创建一个实例
@Controller('code')
@UseGuards(new RulesGuard())
export class CodeController {}

如果要使用全局守卫,使用 app 上面的 useGlobalGuards() 方法

const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());

拦截器

Interceptors

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');

const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}

自定义路由装饰器

Custom route decorators

import { Reflector } from '@nestjs/core';

export const Roles = Reflector.createDecorator<string[]>();