NestJS03-Controllers(下)

1,738 阅读5分钟

接着上一篇的内容,继续学习Controllers。

子域路由

  • @Controller装饰器可以通过host参数来指定一些特殊的路由
@Controller({host: 'admin.example.com'})
export class AdminController {
  @Get()
  index(): string {
    return 'Admin page';
  }
}

效果是只有在这个域名下才能访问这个接口,我们修改下本机的hosts文件,加入下面的内容,然后打开浏览器分别用localhost和admin.example.com来访问下接口看下结果

127.0.0.1 admin.example.com

localhost返回404 localhost.jpg

admin.example.com正常返回 admin.jpg

  • Path类似,host也可以是动态的内容,通过@HostParam()装饰器可以取到相应的值
/**
 * :acccount 说明这个地方是动态的
 */
@Controller({ host: ':account.example01.com' })
export class AccountController {
  @Get()
  // 通过HostParam 去的account的实际内容
  getInfo(@HostParam('account') account: string) {
    return account;
  }
}

本机的hosts文件增加

127.0.0.1 admin01.example01.com

访问结果可以看出,正确的取得admin01

admin01.jpg

作用域

对于来自不同编程语言背景的人来说,在Nest中,几乎所有内容都在传入的请求中共享,这可能是出乎意料的。我们有一个到数据库的连接池、具有全局状态的单例服务等。请记住,Node.js不遵循请求/响应多线程无状态模型,其中每个请求都由一个单独的线程处理。因此,使用单实例对我们的应用程序来说是完全安全的。

然而,在一些边缘情况下,控制器基于请求的生存期可能是所需的行为,例如GraphQL应用程序中的每个请求缓存、请求跟踪或多租户。这时候需要了解怎么控制作用域。这里暂时不做介绍。有兴趣的朋友可以自己了解下Injection scopes

非同步

Nest支持异步操作async/await。异步函数会返回一个Promise

@Controller('cats')
export class CatsController {
  /**
   * 非同步
   * @returns []
   */
  @Get()
  async findAll(): Promise<any[]> {
    return [];
  }
}

不仅如此,Nest还支持RxJS观察订阅模式

import { Controller, Get } from '@nestjs/common';
import { Observable, of } from 'rxjs';

@Controller('cats')
export class CatsController {
  @Get()
  findAll_ob(): Observable<any[]> {
    return of([]);
  }
}

Request payloads

这里引出了一个DTO的概念,Data Transfer Object。个人理解是各个层之间为了数据通信的格式和内容都能符合自己的需求,在通信之间有一个数据转换的概念。就比如说web用Json内容请求的服务端,其实网络通信以后给过去的数据都是字符串。但是服务端可能要转换成各种类型,数字类型,日期类型。另外,前端给过来的数据可能会携带垃圾数据,Dto能够很好的过滤掉这层数据。

Nest里面DTO的写法一般是写成一个类,而不是TypeScrpt那种写成interface。因为TypeScript转换成JS的时候会去除这部分代码,Nest不能利用这部分代码做更多的事情。比如和管道相互结合去除不想要的字段,返回给前端的内容里面去掉敏感数据等等。

// src/cats/dto/create-cats.dto
export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}
// src/cat/cats.controller.ts
@Controller('cats')
export class CatsController {
  /**
   * Request payloads
   */
  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    return createCatDto;
  }
}

异常处理

可以通过全局的拦截器来处理一些共通的异常,也可以在业务逻辑中单独处理异常,内容较多,这里不做介绍,有兴趣可以查看官网的异常过滤

比较全的例子

Bind装饰器的写法省略。

@Controller('cats')
export class CatsController {
  /**
   * 比较全的例子
   */
  @Post('sample')
  create_samplel(@Body() createCatDto: CreateCatDto) {
    return 'This action adds a new cat';
  }

  @Get('sample')
  findAll_sample(@Query() query: ListAllEntities) {
    return `This action returns all cats (limit: ${query.limit} items)`;
  }

  @Get('/sample/:id')
  findOne_sample(@Param('id') id: string) {
    return `This action returns a #${id} cat`;
  }

  @Put('/sample/:id')
  update_sample(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
    return `This action updates a #${id} cat`;
  }

  @Delete(':id')
  remove_sample(@Param('id') id: string) {
    return `This action removes a #${id} cat`;
  }
}

启动和运行

仅仅只写了控制器的代码,Nest并不能识别并且运行。我们需要把它放到一个@Module模块里面. 打开项目的总模块app.module.ts文件,可以看到在imports里面导入了我们之前所写的模块。

import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
import { AccountModule } from './account/account.module';
import { AdminModule } from './admin/admin.module';

@Module({
  // 导入各种模块
  imports: [CatsModule, AccountModule, AdminModule],
  controllers: [],
  providers: [],
})
export class AppModule {}

打开cats目录下的cats.module.ts,在这里引用了cats的控制器

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';

@Module({
  controllers: [CatsController]
})
export class CatsModule {}

Library-specific approach

到目前为止,我们已经讨论了操纵响应的Nest标准方法。处理响应的第二种方法是使用特定于库的响应对象。为了注入特定的响应对象,我们需要使用@Res()修饰符。为了显示差异,让我们将CatsController重写为以下内容:

import { Controller, Get, Post, Res, HttpStatus } from '@nestjs/common';  
import { Response } from 'express';

@Controller('cats')
export class CatsController {
  
  /**
   * Library-specific approach
   */
  @Post('specific')
  create_specific(@Res() res: Response) {
    res.status(HttpStatus.CREATED).send();
  }

  @Get('specific')
  findAll_sepcific(@Res() res: Response) {
     res.status(HttpStatus.OK).json([]);
  }
}

虽然这种方法有效,并且实际上通过提供对响应对象的完全控制(头操作、库特定特性等),在某些方面允许更大的灵活性,但应该谨慎使用。总的来说,这种方法不太清楚,确实存在一些缺点。主要的缺点是代码变得依赖于平台(因为底层库在响应对象上可能有不同的API),并且更难测试(您必须模拟响应对象等)。

此外,在上面的示例中,您失去了与依赖于Nest标准响应处理的Nest特性的兼容性,例如Interceptor和@HttpCode()/@Header()修饰符。要解决此问题,可以将passthrough选项设置为true,如下所示:

@Controller('cats')
export class CatsController {
  @Get()
  findAll_specific2(@Res({ passthrough: true }) res: Response) {
    res.status(HttpStatus.OK);
    return [];
  }
}

代码地址

代码