最佳实践MVC模式的Node框架Nest.JS最全介绍

Middleware中间件

中间件是用来做什么的?

  • 执行任何代码
  • 更改请求/响应对象
  • 结束请求-响应周期
  • 调用堆栈中的下一个中间件函数
  • 如果当前的中间件函数没有结束请求-响应周期,那么必须调用next()将控制权传递给下一个中间件函数。否则请求会一直停留在挂起状态。

使用中间件

Nest没有实现中间件装饰器,我们使用模块类的configure方法。因此需要使用中间件的模块都需要implements NestModule

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}

使用方法

  1. 路由名称 forRoutes('cats')
  2. 控制器forRoutes(CatsController)
  3. 具体请求类型forRoutes({ path: 'cats', method: RequestMethod.GET })
  4. 路由通配符forRoutes({ path: 'ab*cd', method: RequestMethod.ALL })
  5. 剔除某些请求
consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET },
    { path: 'cats', method: RequestMethod.POST },
    'cats/(.*)',
  )
  .forRoutes(CatsController);

函数式中间件

我们可以将中间件写成一个函数:

import { Request, Response } from 'express';

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

如何使用函数式中间件

consumer
  .apply(logger)
  .forRoutes(CatsController);

全局使用

INestApplication实例有一个use()方法:

const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);

Filters异常过滤器

什么是Filters

Nest带有内置的异常处理层,该层负责处理整个应用中所有未被处理的异常。 当你的代码未处理异常时,该层将捕获该异常,然后发送对用户比较友好的响应回去。

内置的HttpException

Exception filters机制是开箱即用的,此操作由内置的全局异常过滤器执行,该过滤器处理HttpException类(及其子类)的异常。如果无法识别异常(既不是HttpException也不是从HttpException继承的类),则内置异常过滤器将生成以下默认JSON响应:

{
  "statusCode": 500,
  "message": "Internal server error"
}

我们可以随意使用内置的HttpException捕获:

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

这样的话客户端会收到这样的响应:

{
  "statusCode": 403,
  "message": "Forbidden"
}

要仅覆盖JSON响应主体的消息部分,我们只需要在response参数中提供一个字符串。 要覆盖整个JSON响应主体,我可以在response参数中传递一个对象。比如:

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

这样的话,响应就是下面的样子了:

{
  "status": 403,
  "error": "This is a custom message"
}

内置HttpException子类方便使用

Nest为我们提供了一组继承于HttpException的异常Filters,比如:

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException

所以,下面的代码作用是相同的:

 throw new HttpException('user not exist',HttpStatus.BAD_REQUEST)
 throw new BadRequestException('User not exist');

为什么要自定义Filters

虽然内置Exception Filter可以为您自动处理许多情况,但您可能希望完全控制异常层。 例如,您可能要添加日志记录或基于一些动态因素使用其他JSON Schema。 异常过滤器正是为此目的而设计的。它们使您可以把握精确的控制流以及将响应的内容发送回客户端。

如何自定义一个Filter

例如我们自定义一个HttpExceptionFilter

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        message:exception.message,
        error:exception.name,
        timeStamp: new Date().toString(),
        path: request.url
      });
  }
}

所有的异常处理过滤器都需要implements通用的ExceptionFilter<T>接口,这需要你去实现catch(exception: T, host: ArgumentsHost)方法。T表示exception的类型。

然后客户端收到的异常处理就会是类似这样的:

{
   "statusCode": 401,
   "message": "Unauthorized",
   "error": "Error",
   "timeStamp": "Thu Jul 09 2020 11:38:07 GMT+0800 (中国标准时间)",
   "path": "/user/aaa"
}

上面的自定义Filters代码里,@Catch(HttpException)装饰器将所需的元数据绑定到异常过滤器,它的作用其实很简单,仅仅是告诉Nest这个过滤器正在寻找HttpException类型的异常,没什么其他复杂的东西。 @Catch()装饰器可以采用单个参数,也可以采用逗号分隔的列表。 这样可以同时为几种类型的异常设置过滤器。

Arguments Host

让我们看一下catch()方法的参数。exception是当前正在处理的异常对象,host参数是ArgumentsHost对象。 ArgumentsHost是一个功能强大的实体对象。在此代码示例中,我们只是使用它获取被传递给原始请求处理(在异常发生所在的控制器中)的RequestResponse对象的引用。我们在ArgumentsHost上使用了一些辅助方法来获取所需的RequestResponse对象。在执行上下文章节了解有关ArgumentsHost的更多信息。

将Filters绑定到使用的地方

  • Method
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
  • Controller
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
  • Global
  1. 使用useGlobalFilters
const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());

全局拦截器useGlobalInterceptors在整个应用中用于每个controller和每个routes。在依赖注入方面,从任何模块外部注册的全局拦截器(如使用useGlobalInterceptors)都无法注入依赖关系,因为这是在所有模块上下文之外完成的。为了解决这个问题,我们可以在任意模块里直接用下面的方式设置interceptor

  1. 使用任意Module

import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

Pipes管道

管道是用@Injectable()装饰器注释的类。管道均应实现PipeTransform接口。

Pipes是用来干嘛的

  • 转换:将输入的数据转换为需要的格式(比如将字符串转为整型)
  • 验证:验证输入的数据,如果正确就简单的返回就好,如果不正确抛出一个异常(比如常见的验证用户id是否存在)

在这两种情况下,管道都对由controller route handler处理的arguments进行操作。 Nest会在调用方法之前插入一个管道,并且管道会接收参数并对其进行操作。此时将进行任何转换/验证操作,然后使用转换后的参数调用route handler

例如,ParseIntPipe管道将方法处理程序参数转换为JavaScript整型(如果转换失败,则抛出异常)。

内置的Pipes

Nest为我们提供了5个开箱即用的Pipe。

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • DefaultValuePipe

如何/在哪里使用内置管道

将管道类的实例绑定到适当的上下文。在我们的ParseIntPipe示例中,我们希望将管道与特定的路由处理方法相关联,并确保它在该方法被调用之前运行。 我们使用以下方式,可以将其称为在方法参数级别(method parameter)绑定管道:

@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
  return this.catsService.findOne(id);
}

如果我们传递了字符串而不是数字就会收到这样的响应:

{
  "statusCode": 400,
  "message": "Validation failed (numeric string is expected)",
  "error": "Bad Request"
}

这个异常会阻止 findOne方法执行。

传递Pipe类还是Pipe实例对象

在上面的示例中,我们传递了一个类(ParseIntPipe),而不是实例,我们将实例化的任务留给了框架并允许依赖注入。Pipe和Guard一样,我们可以传递实例对象。 如果我们要通过一些配置选项来丰富内置管道的行为,那么传递即时实例对象非常有用。例如:

@Get(':id')
async findOne(
  @Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))
  id: number,
) {
  return this.catsService.findOne(id);
}

这样我们收到的异常状态码就是406 Not Acceptable

{
    "statusCode": 406,
    "message": "Validation failed (numeric string is expected)",
    "error": "Error",
    "timeStamp": "Thu Jul 09 2020 13:38:07 GMT+0800 (中国标准时间)",
    "path": "/user/aaa"
}

自定义Pipes

比如自定义一个验证用户是否存在的Pipe:

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException, HttpException, HttpStatus } from '@nestjs/common';
import { UsersService } from 'src/users/users.service';

@Injectable()
export class UserByIdPipe implements PipeTransform<string> {
    constructor(private readonly usersService: UsersService) { }
    async transform(value: string, metadata: ArgumentMetadata) {
        const user = await this.usersService.findOne(value);
        if (user) {
            return user;
        } else {
            throw new BadRequestException('User not exist');
        }
    }
}

PipeTransform <T,R>是必须由任何管道实现的通用接口。 通用接口使用T表示输入值的类型,并使用R表示transform()方法的返回类型。

每个管道都必须实现transform()方法以实现PipeTransform接口。 此方法有两个参数:

  • value
  • metedata

value参数是当前处理的方法参数,也就是传递进来的值。而metadata是当前处理的方法参数的元数据。元数据对象具有以下属性:

export interface ArgumentMetadata {
  type: 'body' | 'query' | 'param' | 'custom';
  metatype?: Type<unknown>;
  data?: string;
}

使用自定义管道

 @Get(':id')
    getProfile(@Request() req, @Param('id', ParseIntPipe, UserByIdPipe) user: User) {
        return user;
    }

如果用户存在,会返回用户信息。如果用户不存在,客户端会收到这个响应:

{
    "statusCode": 400,
    "message": "User not exist",
    "error": "Error",
    "timeStamp": "Thu Jul 09 2020 13:45:38 GMT+0800 (中国标准时间)",
    "path": "/user/22"
}

有时候我们不希望直接返回用户信息,因为我们可能是在删除/注册用户之前,先验证一下。
在管道里:

 if (user) {
            return value;
        } else {
            throw new BadRequestException('User not exist');
        }

如何使用:

 @Delete(':id')
    remove(@Param('id', ParseIntPipe, UserByIdPipe) id: string): Promise<void> {
        return this.usersService.remove(id);
    }

转换数据的使用场景

验证不是定制管道的唯一场景。在前面我们提到管道还可以将输入数据转换为所需的格式,因为从transform方法返回的值将完全覆盖参数的先前值。

什么时候用转换呢?有时从客户端传递的数据需要进行一些更改(例如,将字符串转换为整数),然后才能通过路由处理方法对其进行正确处理。 此外,某些必填数据字段可能会丢失,我们希望可以使用默认值。转换管道可以通过在客户端请求和请求处理方法之间插入处理逻辑来完成这些功能。

下面的例子是一个我们自定义的ParseIntPipe,它负责将字符串解析为整数值。 (如上所述,Nest有一个内置的ParseIntPipe,它更加复杂;这里我们仅将下面这个管道作为自定义转换管道的简单示例)。

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}

默认值填充管道

默认值管道DefaultValuePipe的使用

@Get()
async findAll(
  @Query('activeOnly', new DefaultValuePipe(false), ParseBoolPipe) activeOnly: boolean,
  @Query('page', new DefaultValuePipe(0), ParseIntPipe) page: number,
) {
  return this.catsService.findAll({ activeOnly, page });
}

如何使用管道

  • Parameter

    当验证逻辑只关心某个特定参数的时候,参数范围的管道非常有用

    
    @Get(':id')
      async findOne(@Param('id', ParseIntPipe) id: number) {
        return this.catsService.findOne(id);
      }
    
    
     @Post()
     async create(
        @Body(new ValidationPipe()) createCatDto: CreateCatDto) {
           this.catsService.create(createCatDto);
      }
    
  • Golbal

    1. 使用useGlobalPipes
    const app = await NestFactory.create(AppModule);
    app.useGlobalPipes(new ValidationPipe());
    
    1. 使用任意Module
    import { Module } from '@nestjs/common';
    import { APP_PIPE } from '@nestjs/core';
    
    @Module({
       providers: [
        {
          provide: APP_PIPE,
          useClass: ValidationPipe,
        },
          ],
      })
     export class AppModule {}
    

Guards路由守卫

什么是Guards

Guards是单一职责的。它们根据运行时出现的某些条件(例如权限,角色,ACL等)来确定给定的请求是否由路由处理程序处理。这通常称为授权。授权(以及它通常与之合作的东西——身份验证)通常由传统Express应用中的中间件处理。中间件是身份验证的不错选择,因为像令牌验证,或者向请求对象上添加属性等这样的事情,与路由之间没有强烈的关联。

但是,中间件有些其他问题。在调用next()函数后,它不知道哪个处理程序会被执行。而Guards可以访问ExecutionContext实例,因此确切知道下一步将要执行什么。它们的设计非常类似于异常过滤器,管道和拦截器,使您可以在请求/响应周期中的正确位置插入处理逻辑。

Guards在中间件之后,拦截器/管道之前执行。

如何实现Guards

如前所述,AuthorizationGuards的一个很好的用例,因为只有当调用者(通常是经过身份验证的特定用户)具有足够的权限时,特定的路由才可用。 现在,我们将构建的AuthGuard假设已通过身份验证的用户(Token已经加到了请求Headers中)。它将提取并验证token,并使用提取的信息来确定请求是否可以继续。

关于JWT Token Auth 验证,后面我会写篇文章讲如何实现。

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

每个Guards都必须实现canActivate()函数。 此函数应返回一个布尔值,表示是否允许当前请求。它可以同步或异步(通过Promise或Observable)的返回响应。Nest使用返回值控制下一步操作:

  • 如果返回true,则将处理请求。
  • 如果返回false,则Nest将拒绝该请求。

Execution context

canActivate()函数只有一个参数,即ExecutionContext实例。ExecutionContext继承自ArgumentsHost。我们上文在异常过滤器中看到过ArgumentsHost。在上面的示例中,我们只是使用与我们之前使用的ArgumentsHost上定义的相同的方法来获取对Request对象的引用。

通过继承ArgumentsHostExecutionContext还添加了几个方法,这些方法提供有关当前执行过程的更多详细信息。这些信息可以帮助我们构建可以在控制器、方法、执行上下文中工作的Guards。在此处了解有关ExecutionContext的更多信息。

如何使用Guards

  • Controller
    @Controller('cats')
    @UseGuards(RolesGuard)
    export class CatsController {}
    
  • Method
    @UseGuards(RolesGuard)
    @Delete(':id')
    remove(@Param('id', ParseIntPipe, UserByIdPipe) id: string): Promise<void> {
          return this.usersService.remove(id);
    }
    
  • Global
    1. 使用useGlobalGuards
    const app = await NestFactory.create(AppModule);
    app.useGlobalGuards(new RolesGuard());
    
    1. 使用module
    import { Module } from '@nestjs/common';
    import { APP_GUARD } from '@nestjs/core';
    
    @Module({
    providers: [
      {
        provide: APP_GUARD,
        useClass: RolesGuard,
      },
      ],
     })
     export class AppModule {}
    

设置角色

我们的RolesGuard可以工作了,但是它还未利用最重要的Guards功能—— execution context(执行上下文)。它还不了解每个处理程序允许哪些角色访问。例如,对于不同的路由,CatsController可能具有不同的权限方案。有些可能仅对管理员用户可用,而另一些可能对所有人开放。 我们如何将角色与路由匹配?

这里就要说到自定义元数据了解更多)了。Nest提供了通过@SetMetadat()装饰器将自定义元数据附加到路由的功能。该元数据提供了我们缺少的角色数据。 让我们看一下使用@SetMetadata()的方法:

@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

我们将角色元数据(roles是元数据key,['admin']是value)附加到create()方法。直接在路由里使用@SetMetadata()不是好的写法,我们可以将其封装成一个@Role()装饰器。

import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

然后在Controller里面:

@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

实现基于角色的Guards

根据路由的元数据角色信息,我们在Guards里判断用户是否有权进入路由:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean>{
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    const hasRole = () =>
      user.roles.some(role => !!roles.find(item => item === role));

    return user && user.roles && hasRole();
  }
}

通过request.user获取用户信息,是因为这里我们假设应用里已经做了Token验证并且上下文存储了用户信息,这一般是每个应用都必不可少的事情。

有关以上下文相关的方式使用Reflector的更多详细信息,请参见Execution context反射和元数据部分。

这样的话,如果用户不是admin角色,会收到下面的响应:

{
  "statusCode": 403,
  "message": "Forbidden resource",
  "error": "Forbidden"
}

框架默认使用ForbiddenException抛出异常,当然,如果你不想用默认的,你也可以在Guards里抛出你自己的异常,比如:

throw new UnauthorizedException();

Interceptors拦截器

拦截器是干嘛的?

  • 在方法执行之前/之后加入我们自己的逻辑
  • 转换/包装一个方法返回的结果
  • 转换/包装一个方法抛出的异常
  • 扩展基础方法的内容
  • 根据某些特殊情况(比如缓存的目的)完全覆盖/重写一个方法

使用范围

  • Controller
@UseInterceptors(LoggingInterceptor)
export class CatsController {}
  • Method
 @UseInterceptors(LoggingInterceptor)
 @Get()
 async findAll(): Promise<Cat[]> {
   return this.catsService.findAll();
 }
  • Global
  1. 使用useGlobalInterceptors
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
  1. 使用任意Module
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
  ],
})
export class AppModule {}

Custom Decorators自定义装饰器

什么是装饰器

Nest是围绕一种称为装饰器的语言功能构建的。装饰器的概念可以简单总结为:

ES6装饰器是一个返回函数的表达式,可以将目标,名称和属性描述作为参数。 您可以通过在装饰器前面加上@字符来应用它,并将其放在要装饰的内容的最顶部。 内容可以为类,方法或属性。

如何自定义装饰器

当装饰器的行为取决于某些条件时,可以使用data参数将参数传递给装饰器的工厂函数。 一个常用的场景是自定义装饰器,该装饰器可以通过键从请求对象中提取属性。 例如下面通过传递username字段来获取值,如果User对象比较复杂的话,这样可以更简易的获取数据,可读性也更好。

user.decorator.ts

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;

    return data ? user && user[data] : user;
  },
);

user.controller.ts

 @Get('username')
 async findOne(@UserDecorator('username') username: string) {
       return `Hello ${username}`;
}

和管道一起使用

Nest对自定义装饰器和内置的待遇一样,我们可以同内置的一样使用管道验证等。

@Get()
async findOne(@User(new ValidationPipe()) user: UserEntity) {
  console.log(user);
}

组合装饰器

Nest提供了一种辅助方法来组成多个装饰器。例如,假设你要将与身份验证相关的所有装饰器组合到一个装饰器中。可以通过以下方式实现:

import { applyDecorators } from '@nestjs/common';

export function Auth(...roles: Role[]) {
  return applyDecorators(
    SetMetadata('roles', roles),
    UseGuards(AuthGuard, RolesGuard),
    ApiBearerAuth(),
    ApiUnauthorizedResponse({ description: 'Unauthorized"' }),
  );
}

然后在Controller里面:

@Get('users')
@Auth('admin')
findAllUsers() {}

findAllUsers现在就具有通过一个声明应用四个装饰器的效果。

其实经过Nest框架作者Kamil Mysliwiec的精心设计,Nest使用起来非常简单方便,而且扩展是相当的灵活和强大。但是我们必须理解Nest的这些概念是用来干嘛的,这样我们才知道什么时候去实现一个Filter,什么时候去实现一个Pipe。或者知道Middleware和Guards的区别。尤其是Interceptor,好像它哪里都可以使用。如果不好好理解官方的文档,很容易使用不当。

后面我会更新更多关于Nest的使用技巧,比如如何实现JWT Token验证,如何设计环境变量/全局数据注册,或者其他有趣的知识。

旧时茅店社林边,路转溪桥忽见。很高兴认识你,陌生人。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一套目前来说最好的nestjs实战教程,提供QQ长期问答服务. 本人从 08 年到 18 年一直从事于 PHP 的开发。从 18 年开始转向 Typescript+React+Nestjs 的技术栈。目前来说 React 应该是一个非常好用的前端框架,生态非常完善,并且十分灵活简单。Nestjs 则是 Node.js 中唯一且无敌存在的后端 web 框架。因为我个人从事这套技术栈开发已经 4 年多,所以颇有心得,做了这套 React18 视频教程和 Nestjs 实战视频教程。现在视频教程也是刚刚开始做了一部分,还在持续更新。使用 TS 栈开发可以基本涵盖各种平台的方方面面,比如开发桌面应用的 Electron, 开发小程序的 Taro, 开发 Spa 中后台的 React,开发 SSR 网站的 next.js,开发移动应用的 React Native, 开发 CLI 的 Yargs, 以及开发后端的 Nestjs。基本学会一套,面够用,再加上 Monorepo 组织结构,一个仓库所有平台都可以搞定。 包含以下知识点 - 掌握Nestjs框架的依赖注入,模块,提供者,生命周期等概念- 掌握DTO数据验证,响应序列化,异常过滤器等常用功能- 学会编写一些常用的class-validator验证约束- 熟练掌握Typeorm以及Nestjs与Typeorm结合开发- 学会整合Swagger输出Open API文档- 掌握TS装饰器以及反射元数据的定义和使用- 编写一些数据库相关的数据验证约束(比如树形表的同级别某字段唯一验证等)- 学会通过继承并魔改Nestjs源码编写自定义的局验证器- 可以编写自定义的配置系统以及核心功能包- 学会自定义的代码组织方式(比如教程中我把默认的Nestjs应用改成Util+PluginModule模式)- 掌握编写一些常用的Util仓库(比如数据库,Redis,Restful)- 利用Yargs结合魔改后的框架可以编写一些自定义CLI命令(比如数据迁移,数据填充等)- 掌握如何利用阿里云/腾讯云推送邮件和短信- 掌握使用消息列队(MQ)的方式异步推送邮件和短信- 掌握守卫原理以及编写一些用户验证的守卫- 编写一个完善的用户系统(JWT认证,短信/邮件登录,短信/邮件注册,找回密码,绑定手机和邮箱等)- 熟练地通过编写装饰器去实现一些常用的功能- 通过SSE,WebSockets实现用户的上线,下线以及消息实时推送,消息广播等- 学会使用云存储来上传文件- 学会大文件断点雪川- 实现RBAC的权限系统- 理解请求范围概念以及性能方便的考量- 自己构建配置系统,实现配置验证以及通过YAML或数据库来进行动态配置- 通过适用Vscode进行Debug以及编写Jest测试来提升开发效率与程序的可用性- 学会使用Node来编写自定义的CLI命令- 利用NestCURD进行快速开发- 学会Graphql替代Restful写API- 使用Mongodb替代关系型数据库- 掌握一些常用的打包工具,比如通过ncc打包成单文件,通过pack打包成二进制等- 学会一些常用的部署方式,比如通过nginx+pm2反向代理部署,devops自动化CI,CD等- 学会使用pnpm workspaces来使用monreopo组织代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值