Nest.js 框架入门

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 {
  return [];
}
// Observable
@Get()
findAll(): Observable {
  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 {
    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 {
    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 | Observable {
    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('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 {
    console.log('Before...');

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

参考

  • https://docs.nestjs.cn/6/introduction

你可能感兴趣的:(Nest.js 框架入门)