Skip to content

装饰器

Nest 大多数功能都是通过装饰器来实现的,接下来过一遍所有的装饰器。

Nest 提供了模块系统,通过 @Module 来声明模块。

ts
@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

通过 @Controller 来声明 Controller。

ts
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello()
  }
}

通过 @Injectable 来声明 Provider。

ts
@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!'
  }
}

注入的方式可以选择构造器注入

ts
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello()
  }
}

或者使用 @Inject 来属性注入。

ts
@Controller()
export class AppController {
  // constructor(private readonly appService: AppService) {}
  @Inject(AppService)
  private readonly appService: AppService

  @Get()
  getHello(): string {
    return this.appService.getHello()
  }
}

如果注入的依赖不存在,创建对象时会报错。可以通过 @Optional 来声明该依赖是可选的,即使没有对应的 Provider 也会创建对应的对象。

ts
@Controller()
export class AppController {
  @Optional()
  @Inject()
  private readonly aaa: Record<string, any>

  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello()
  }
}

如果一个模块在很多地方都会被引用,可以通过 @Global 来声明为全局模块。全局模块 exports 的 Provider 可以直接注入。

ts
@Global()
@Module({
  controllers: [AaaController],
  providers: [AaaService],
  exports: [AaaService],
})
export class AaaModule {}

Filter 用于处理未捕获的异常,通过 @Catch 来指定。

ts
@Catch()
export class BbbFilter<T> implements ExceptionFilter {
  catch(exception: T, host: ArgumentsHost) {}
}

通过 @UseFilters 作用在 Controller 上。

ts
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  @UseFilters(BbbFilter)
  getHello(): string {
    return this.appService.getHello()
  }
}

除了 Filter 之外,Interceptor、Guard、Pipe 都是通过 @UseXXX 的方式注入。

不过,Pipe 更多在某个单独参数中使用。

ts
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: string) {
  return this.aaaService.findOne(+id);
}

@Param 用于取出路径中的参数,@Query 用于取出 url 后的参数。

ts
@Get(':id')
findOne(
  @Param('id', ParseIntPipe) id: string,
  @Query('bbb', ParseBoolPipe) bbb: boolean,
) {
  console.log(id, bbb);

  return this.aaaService.findOne(+id);
}

@Body 用于取出请求中的 body 部分。

ts
@Post()
create(@Body() createAaaDto: CreateAaaDto) {
  return this.aaaService.create(createAaaDto);
}

除了 @Post 外,@Get@Put@Delete@Patch@Options@Head 装饰器可以分别接受对应的 HTTP 请求。

Handler 或 Class 可以通过 @SetMetadata 指定 meatadata。

ts
@Controller('aaa')
@SetMetadata('roles', ['user'])
ts
@Get()
@SetMetadata('roles', ['admin'])
findAll() {
  return this.aaaService.findAll();
}

然后在 Guard 或 Interceptor 中取出。

ts
@Injectable()
export class CccGuard implements CanActivate {
  @Inject()
  private readonly reflector: Reflector

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const classMetaData = this.reflector.get('roles', context.getClass())
    const methodMetaData = this.reflector.get('roles', context.getHandler())

    console.log(classMetaData, methodMetaData)

    return true
  }
}

@Headers 可以获取请求头。

ts
@Get('/headers')
headers(
  @Headers('Accept') accept: string,
  @Headers() headers: Record<string, any>,
) {
  console.log(accept);
  console.log(headers);
}

@Ip 拿到请求的 IP。

ts
@Get('/ip')
ip(@Ip() ip: string) {
  console.log(ip);
}

@Session 拿到请求的 session。

ts
@Get('/session')
session(@Session() session: string) {
  console.log(session);
}

@HostParma 用于取出域名部分的参数。

ts
@Controller({ host: ':host.0.0.1', path: 'aaa' })
export class AaaController {
  constructor(private readonly aaaService: AaaService) {}

  @Get()
  host(@HostParam('host') hostParma) {
    console.log(hostParma)

    return hostParma
  }
}

@Req@Request 用于取出 Request 对象。

ts
@Get()
req(@Req() req: Request) {
  return req.url;
}

@Res@Response 用于取出 Response 对象。

ts
@Get()
res(@Res() res: Response) {
  return 'res';
}

如果自己注入了 Response 对象,而不返回响应,服务器会一直没有响应。这是因为 Nest 不会再把 Handler 返回值作为响应内容了。这时需要自己返回响应。

ts
@Get('res')
res(@Res() res: Response) {
  return res.end('hello');
}

这么做是为了避免自己返回的响应和 Nest 返回的响应冲突。如果不需要自己处理响应,可以通过 passthrough 告诉 Nest。

ts
@Get()
res(@Res({ passthrough: true }) res: Response) {
  return 'res';
}

除了 @Res 不会返回响应外,@Next 也不会。

ts
@Get()
res(@Next() next: NextFunction) {
  return 'res';
}

当有两个 Handler 处理同一个路由的时候,可以在第一个 Handler 里注入 Next,调用它来将请求转发到第二个 Handler。

ts
@Get()
  res(@Next() next: NextFunction) {
  console.log('1');

  next();
}

@Get()
res2(@Next() next: NextFunction) {
  console.log('2');

  next();
}

@HttpCode 可以修改 HTTP 响应码。

ts
@Get()
@HttpCode(404)
res() {
  return 'hello';
}

@Header 可以修改响应头。

ts
@Get()
@Header('aaa', 'bbb')
res() {
  return 'hello';
}

@Redirect 用于指定路由重定向的 url。

ts
@Get()
@Redirect('https://baidu.com')
res() {}

至此,我们梳理了一遍常用的装饰器。

Released under the MIT License.