美文网首页
Nest.js 框架入门

Nest.js 框架入门

作者: 风之化身呀 | 来源:发表于2019-10-28 16:23 被阅读0次

    Nest 提供了一个开箱即用的应用程序架构,支持Typescript,和 Angular 架构十分相似,基本模式如下:

    src
    ├── app.controller.ts
    ├── app.module.ts
    └── main.ts
    // main.ts
    import { NestFactory } from '@nestjs/core';
    import { ApplicationModule } from './app.module';
    
    async function bootstrap() {
      const app = await NestFactory.create(ApplicationModule);
      await app.listen(3000);
    }
    bootstrap();
    

    然后根模块里导入各种功能模块。

    1、控制器

    控制器可理解为路由,专门负责处理各种HTTP请求(GET,POST等)

    1.1、基本用法

    使用 Controller 装饰器定义一个控制器(可接受字符串作为路径),配合 Get,Post 等装饰器形成路由:

    import { Controller, Get } from '@nestjs/common';
    
    @Controller('cats')
    export class CatsController {
      @Get()
      findAll(): string {
        return 'test';
      }
    }
    // 访问,output:test
    curl -X GET localhost:3000/cats
    

    Get等方法装饰器也可以接受参数:

    import { Controller, Get } from '@nestjs/common';
    
    @Controller('v1')
    export class CatsController {
      @Get('cats')
      findAll(): string {
        return 'test';
      }
    }
    // 访问,output:test
    curl -X GET localhost:3000/v1/cats
    

    注意:这里的 findAll 的名称可以是任意的,从访问地址中也可看出。也不能用箭头函数(装饰器不支持),最后一点就是 controller 必须依附在 module 里:

    // app.module.ts
    import { Module } from '@nestjs/common';
    import { AppController } from './app.controller';
    import { AppService } from './app.service';
    import { CatsController } from './cats/cats.controller';
    
    @Module({
      imports: [],
      controllers: [AppController, CatsController],
      providers: [AppService],
    })
    export class AppModule {}
    

    1.2、路由相关

    1、获取参数

    import { Controller, Get, Req,Res } from '@nestjs/common';
    import { Request,Response } from 'express';
    
    @Controller('cats')
    export class CatsController {
      @Get()
      findAll(@Req() request: Request,@Res() response:Response): string {
        return 'This action returns all cats';
      }
    }
    

    还有诸如: @Body,@Param,@Query,@Headers. @Put,@Delete,@Patch,@Options,@Head,@All

    2、动态参数 & 路由注册顺序(路由参数都是可选的)

    @Get(':id')
    findOne(@Param() params): string {
      console.log(params.id);
      return `This action returns a #${params.id} cat`;
    }
    // GET /cats 请求不会命中第二个处理程序,因为所有路由参数都是可选的,应该调换位置
    @Controller('cats')
    export class CatsController {
      @Get(':id')
      findOne(@Param('id') id: string) {
        return `This action returns a #${id} cat`;
      }
    
      @Get()
      findAll() {
        // This endpoint will never get called
        // because the "/cats" request is going
        // to be captured by the "/cats/:id" route handler
      }
    }
    

    3、异步接口 Async / await

    // Promise
    @Get()
    async findAll(): Promise<any[]> {
      return [];
    }
    // Observable
    @Get()
    findAll(): Observable<any[]> {
      return of([]);
    }
    

    4、请求负载 DTO
    这里使用类而不是接口来定义 DTO,因为接口在TS编译时会被删除,而类不会。这有助于其他特性的使用(管道等特性能够在访问变量的元类型时提供更多的可能性)

    export class CreateCatDto {
      readonly name: string;
      readonly age: number;
      readonly breed: string;
    }
    
    @Post()
    async create(@Body() createCatDto: CreateCatDto) {
      return 'This action adds a new cat';
    }
    
    

    2、提供者

    控制器在处理 HTTP 请求时,应该将更复杂的任务委托给提供者。service、helper等都可以称为提供者。

    2.1、定义

    使用 @Injectable 装饰器定义一个提供者

    // cat.service.ts
    import { Injectable } from '@nestjs/common';
    import { Cat } from './interfaces/cat.interface';
    
    @Injectable()
    export class CatsService {
      private readonly cats: Cat[] = [];
    
      create(cat: Cat) {
        this.cats.push(cat);
      }
    
      findAll(): Cat[] {
        return this.cats;
      }
    }
    

    2.2、注册该提供者

    和控制器一样,提供者也需要依附于module中才能使用

    // app.module.ts
    import { Module } from '@nestjs/common';
    import { CatsController } from './cats/cats.controller';
    import { CatsService } from './cats/cats.service';
    
    @Module({
      controllers: [CatsController],
      providers: [CatsService],
    })
    export class ApplicationModule {}
    

    2.3、调用该提供者

    import { Controller, Get, Post, Body } from '@nestjs/common';
    import { CreateCatDto } from './dto/create-cat.dto';
    import { CatsService } from './cats.service';
    import { Cat } from './interfaces/cat.interface';
    
    @Controller('cats')
    export class CatsController {
      constructor(private readonly catsService: CatsService) {}
    
      @Post()
      async create(@Body() createCatDto: CreateCatDto) {
        this.catsService.create(createCatDto);
      }
    
      @Get()
      async findAll(): Promise<Cat[]> {
        return this.catsService.findAll();
      }
    }
    

    3、模块

    Nest 用它来组织应用程序结构,每个 Nest 应用程序至少有一个模块,即根模块。然后根模块包含各种功能模块,从而组织起应用程序的结构

    3.1、功能模块

    CatsController 和 CatsService 属于同一个应用程序域。 应该考虑将它们移动到一个功能模块下,即 CatsModule:

    import { Module } from '@nestjs/common';
    import { CatsController } from './cats.controller';
    import { CatsService } from './cats.service';
    
    @Module({
     controllers: [CatsController],
     providers: [CatsService],
     exports: [CatService]  // 每个导入CatsModule的模块都可以访问 CatsService
    })
    export class CatsModule {}
    
    // app.module.ts 中引入即可
    import { Module } from '@nestjs/common';
    import { CatsModule } from './cats/cats.module';
    
    @Module({
     imports: [CatsModule],
    })
    export class ApplicationModule {}
    
    

    3.2、全局模块

    由于Nest 将提供者封装在模块范围,故想使用一些诸如 Helper 之类很通用的提供者时必须导入封装它们的模块。这有时候很不方便,故可以使用全局模块:

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

    @Global 装饰器使模块成为全局作用域,然后 CatsService 提供者可以在任意地方注入

    3.3、动态模块

    动态模块扩展了模块元数据。当您需要动态注册组件时,这个实质特性非常有用。

    // database.module.ts
    import { Module, DynamicModule } from '@nestjs/common';
    import { createDatabaseProviders } from './database.providers';
    import { Connection } from './connection.provider';
    
    @Module({
      providers: [Connection],
    })
    export class DatabaseModule {
      static forRoot(entities = [], options?): DynamicModule {
        const providers = createDatabaseProviders(options, entities);
        return {
          module: DatabaseModule,
          providers: providers,
          exports: providers,
        };
      }
    }
    
    // app.module.ts
    import { Module } from '@nestjs/common';
    import { DatabaseModule } from './database/database.module';
    import { User } from './users/entities/user.entity';
    
    @Module({
      imports: [DatabaseModule.forRoot([User])],
      exports: [DatabaseModule],
    })
    export class ApplicationModule {}
    
    

    4、中间件

    中间件是一个在路由处理器之前被调用的函数,Nest中有两种写法:

    4.1、@Injectable 中间件

    必须实现 NestMiddleware 类的 use 方法

    // 1、定义
    import { Injectable, NestMiddleware } from '@nestjs/common';
    import { Request, Response } from 'express';
    
    @Injectable()
    export class LoggerMiddleware implements NestMiddleware {
      use(req: Request, res: Response, next: Function) {
        console.log('Request...');
        next();
      }
    }
    //2、使用
    import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
    import { LoggerMiddleware } from './common/middleware/logger.middleware';
    import { CatsModule } from './cats/cats.module';
    
    @Module({
      imports: [CatsModule],
    })
    export class ApplicationModule implements NestModule {  // 必须实现 NestModule 的 configure 方法
      configure(consumer: MiddlewareConsumer) {
        consumer
          .apply(LoggerMiddleware)
          .exclude(
            { path: 'cats', method: RequestMethod.GET },
            { path: 'cats', method: RequestMethod.POST }
          )
          .forRoutes(CatsController);
      }
    }
    
    

    4.2、纯函数中间件

    中间件没有任何依赖关系时,我们可以考虑使用函数式中间件

    // 1、定义
    export function logger(req, res, next) {
      console.log(`Request...`);
      next();
    };
    // 2、使用(同上)
    

    4.3、多中间件 & 全局中间件

    // 多个中间件
    consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
    // 全局中间件
    const app = await NestFactory.create(ApplicationModule);
    app.use(logger);
    await app.listen(3000);
    
    

    5、异常过滤器、管道、守卫、拦截器

    5.1、异常过滤器

    Nest 内置的异常层会处理应用程序中抛出的所有异常,若未捕获到,则返回500错误,能保证用户将收到友好的响应。

    @Get()
    async findAll() {
      throw new HttpException({
        status: HttpStatus.FORBIDDEN,
        error: 'This is a custom message',
      }, 403);
    }
    

    也就是说,我们只需要负责抛出异常即可,异常种类很多,Nest 内置了一些:
    UnauthorizedException、NotFoundException、RequestTimeoutException、NotImplementedException等等。Nest还提供了自定义异常过滤器以及设置过滤器的作用范围(方法范围的,控制器范围的,也可以是全局范围的)

    5.2、管道

    管道的两个作用:1、数据验证;2、转换数据
    语法上,管道是具有 @Injectable() 装饰器的类。管道应实现 PipeTransform 接口的 transform 方法

    import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
    
    @Injectable()
    export class ValidationPipe implements PipeTransform {
      transform(value: any, metadata: ArgumentMetadata) {
        return value;
      }
    }
    

    Nest 内置了 ValidationPipe和ParseIntPipe,使用ValidationPipe 时,需要同时安装:

    npm i --save class-validator class-transformer
    

    使用步骤:
    1、在 dto 文件中 加入验证装饰器

    import { IsString, IsInt } from 'class-validator';
    
    export class CreateCatDto {
      @IsString()
      readonly name: string;
      @IsInt()
      readonly age: number;
      @IsString()
      readonly breed: string;
    }
    

    2、建立 ValidationPipe 类

    import { PipeTransform, Pipe, ArgumentMetadata, BadRequestException } from '@nestjs/common';
    import { validate } from 'class-validator';
    import { plainToClass } from 'class-transformer';
    
    @Injectable()
    export class ValidationPipe implements PipeTransform<any> {
        async transform(value, metadata: ArgumentMetadata) {
          const { metatype } = metadata;
          if (!metatype || !this.toValidate(metatype)) {
              return value;
          }
          const object = plainToClass(metatype, value);
          const errors = await validate(object);
          if (errors.length > 0) {
              throw new BadRequestException('Validation failed');
          }
          return value;
        }
    
        private toValidate(metatype): boolean {
          const types = [String, Boolean, Number, Array, Object];
          return !types.find((type) => metatype === type);
        }
    }
    

    3、应用 ValidationPipe

    // 方法级别范围
    @Post()
    @UsePipes(ValidationPipe)
    async create(@Body() createCatDto: CreateCatDto) {
      this.catsService.create(createCatDto);
    }
    // 全局级别范围
    async function bootstrap() {
      const app = await NestFactory.create(ApplicationModule);
      app.useGlobalPipes(new ValidationPipe());
      await app.listen(3000);
    }
    bootstrap();
    // 如果想做依赖注入
    import { Module } from '@nestjs/common';
    import { APP_PIPE } from '@nestjs/core';
    
    @Module({
      providers: [
        {
          provide: APP_PIPE,
          useClass: CustomGlobalPipe,
        },
      ],
    })
    export class ApplicationModule {}
    

    5.3、守卫

    1、守卫有一个单独的责任。它们确定请求是否应该由路由处理程序处理,守卫在每个中间件之后执行的,但在拦截器和管道之前。
    2、语法上,守卫是一个使用 @Injectable() 装饰器的类。 守卫应该实现 CanActivate 接口的canActivate方法。

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

    3、角色认证
    守卫和管道、异常过滤器一样,都可以是方法级别、全局级别,也可以做依赖注入

    import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
    import { Observable } from 'rxjs';
    import { Reflector } from '@nestjs/core';
    
    @Injectable()
    export class RolesGuard implements CanActivate {
      constructor(private readonly reflector: Reflector) {}
    
      canActivate(context: ExecutionContext): boolean {
        const roles = this.reflector.get<string[]>('roles', context.getClass()); // 通过反射获取元数据
        if (!roles) {
          return true;
        }
        const request = context.switchToHttp().getRequest();
        const user = request.user;
        const hasRole = () => user.roles.some((role) => roles.includes(role)); // 从user.roles中判断该用户是否拥有该权限,从而实现角色认证
        return user && user.roles && hasRole();
      }
    }
    

    5.4、拦截器

    一条请求会经历:中间件=>守卫=>拦截器=>请求开始=====请求完成=>拦截器=>中间件,从拦截器作用位置可以看出拦截器的功能:
    1、在函数执行之前/之后绑定额外的逻辑
    2、转换从函数返回的结果
    3、转换从函数抛出的异常
    4、根据所选条件完全重写函数 (例如, 缓存目的)
    语法上,拦截器是一个使用 @Injectable() 装饰器的类。 拦截器应该实现 NestInterceptor 接口的intercept方法。

    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`)),
          );
      }
    }
    
    

    参考

    相关文章

      网友评论

          本文标题:Nest.js 框架入门

          本文链接:https://www.haomeiwen.com/subject/qwduuctx.html