NestJs 基础概念

概念

创建项目

npm i -g @nestjs/cli


nest new project-name
复制代码

默认创建了如下的目录

image.png

目录代表意义

app.controller.ts 带有单个路由的基本控制器。
app.controller.spec.ts 针对控制器的单元测试。
app.module.ts T应用程序的根模块(root module)。
app.service.ts 具有单一方法的基本服务(service)。 method.
main.ts 应用程序的入口文件,它使用核心函数 NestFactory 来创建 Nest 应用程序的实例。

启动项目

npm run start
复制代码

main.ts 作为入口文件,包含了一个异步函数,用来引导项目的启动过程,初始化的项目的内容如下:

import { NestFactory } from '@nestjs/core';


import { AppModule } from './app.module';


async function bootstrap() {


  const app = await NestFactory.create(AppModule);


  await app.listen(3000);


}


bootstrap();
复制代码

NestFactory @nestjs/core的核心类,暴露了一些静态方法用于创建应用程序的实例。方法返回一个应用程序的对象,实现了INestApplication接口。

Controllers

概念:负责处理发过来的请求和给客户端返回请求,路由控制器控制哪个 Controller 处理哪个请求,通常一个控制器有多个路由,不同的路有可以执行不同的动作。

image.png

Routing

我们使用 @Controller() 装饰器来定义一个基础的controller,在 @Controller()里面添加一个路由前缀让我们更加轻松的划分路由分组,尽可能减少代码体积。

@@filename(cats.controller)

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}

@@switch
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}
复制代码

@Get() 装饰器在 findAll 的前面,告诉nestHTTP请求创建一个图书的端点,这个端点相当于HTTP请求的的路径和路由路径(处理程序的路由路径是通过连接为控制器声明的(可选的)前缀和在请求装饰器中指定的任何路径来确定的)。Nest将把GET /cats请求映射到这个处理程序,所以我们的路由路径是一个组合的结果。eg:

@@filename(cats.controller)

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

@Controller('customers')

export class CatsController {

  @Get('profile')

  findAll(): string {

    return 'This action returns all /customers/profile';

  }

}
复制代码

Request object

Nest提供对底层平台的请求对象的访问,我们可以添加@Req()装饰器来指示Nest注入请求对象来访问请求对象。

@@filename(cats.controller)


import { Controller, Get, Req } from '@nestjs/common';


import { Request } from 'express';

@Controller('cats')


export class CatsController {


  @Get()


  findAll(@Req() request: Request): string {


    return 'This action returns all cats';

  }

}
复制代码

Request object:代表的是HTTP的请求,拥有很多http请求的属性如:query string 、HTTP headers,HTTP body,在大部分的情况下这些属性在主观上是没有意义的,我们可以使用装饰器来精简这些属性:

名称 代表值
@Request(), @Req() req
@Response(), @Res()* res
@Next() next
@Session() req.session
@Param(key?: string) req.params / req.params[key]
@Body(key?: string) req.body / req.body[key]
@Query(key?: string) req.query / req.query[key]
@Headers(name?: string) req.headers / req.headers[name]
@Ip() req.ip
@HostParam() req.hosts

Resources

我们使用GET 来获取所有的数据,现在我们也可以通过POST 来创建一条数据。

@@filename(cats.controller)


import { Controller, Get, Post } from '@nestjs/common';

@Controller('cats')


export class CatsController {


  @Post()


  create(): string {


    return 'This action adds a new cat';


  }
  @Get()
  findAll(): string {

    return 'This action returns all cats';

  }

}
复制代码

Nest 请求装饰器

@Get()、@Post()、@Put()、@Delete()、@Patch()、@Options()、@Head()、@All()

HTTP 没有 All 方法,这是一个快捷方法用来接收任何类型的 HTTP 请求。

Route wildcards

对路由的匹配也支持通过通配符来进行匹配

@Get('ab*cd')


findAll() {


  return 'This route uses a wildcard';


}
复制代码

上面的 ‘ab*cd’ 可以匹配所有的上诉情况 abcd, ab_cd, abecd,详细情况于正则表达式相匹配

Status code

我们可以通过在处理程序级别添加@HttpCode(…)装饰器轻松地改变状态码。

@Post()


@HttpCode(204)


create() {


  return 'This action adds a new cat';


}
复制代码

Headers

要指定自定义的响应头,你可以使用@Header()装饰器

@Post()


@Header('Cache-Control', 'none')


create() {


  return 'This action adds a new cat';


}
复制代码

Redirection

想要对特殊的路有精心重定向,我们可以使用 @Redirect()@Redirect()接受一个必需的url参数和一个可选的statusCode参数。如果省略,statusCode默认为302 (Found)。

@Get()

@Redirect('https://nestjs.com', 301)
复制代码

返回的值将覆盖传递给@Redirect()装饰器的任何参数

@Get('docs')

@Redirect('https://docs.nestjs.com', 302)

getDocs(@Query('version') version) {

  if (version && version === '5') {

    return { url: 'https://docs.nestjs.com/v5/' };

  }

}
复制代码

Route parameters

为了定义路由的参数,我们可以给参数添加token去在url中标识动态数据,以这种方式声明的路由参数可以使用@Param()装饰器访问,该装饰器应该添加到方法签名中。

@@filename()
@Get(':id')

findOne(@Param() params): string {

console.log(params.id);

return `This action returns a #${params.id} cat`;

}
复制代码

我们可以通过引用params.id来访问id参数。您还可以将特定的参数token传递给装饰器,然后通过方法体中的名称直接引用路由参数。

@@filename()
@Get(':id')

findOne(@Param('id') id: string): string {

    return `This action returns a #${id} cat`;

}
复制代码

Sub-Domain Routing

@Controller装饰器可以采用一个host选项,要求传入请求的HTTP host匹配某些特定的值。

@controller({host:"admin.example.com})

export class AdminController{
    @Get()
    index():string{
        return 'admin page'
    }
}
复制代码

Scopes

在Nest中,几乎所有的东西都是在传入请求之间共享的。存在一个数据库的连接池,带有全局状态的单例服务.

Asynchronicity

JavaScript 数据的提取一般都是异步的,nestjs 是基于JavaScript的,所以,nest对于 async 函数支持的很好。
每一个async 函数必须要返回一个Promise,这意味着你可以返回一个Nest能够自行解析的延迟值。

@controller(cats.controller)

export class AdminController{
  @Get()
    async findAll(): Promise<any[]> {
        return [];
    }
}
复制代码

payloads

我们之前的例子中POST没有处理来自客户端的请求参数,可以使用 @Body 装饰器。
我们使用 TypeScript,我们需要先进行定义DTO(数据传输对象),该对象定义了在网络中数据传输的格式,可以使用 class 和 interface 的方式进行定义,但是更加倾向于使用class进行定义,因为class 符合es6的标准,使用其他的可能存在额外的性能问题。

@@filename(create-cat.dto)
export class CreateCatDto {

    name: string;

    age: number;

    breed: string;
}
复制代码

我们可以在controller 中使用DTO

@@filename(cats.controller)
@Post()
async create(@Body() createCatDto: CreateCatDto) {
    return 'This action adds a new cat';
}
复制代码

Full resource sample

下面是一个使用几种可用装饰器来创建基本控制器的例子。这个控制器公开了几个方法来访问和操作内部数据。

@@filename(cats.controller)
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto';

@Controller('cats')
export class CatsController {
@Post()
create(@Body() createCatDto: CreateCatDto) {
    return 'This action adds a new cat';
}

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

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

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

@Delete(':id')
remove(@Param('id') id: string) {
    return `This action removes a #${id} cat`;
}
}
复制代码

Getting up and running

虽然CatsController已经完全定义好了,但是Nest还是不知道这个 controller 的存在,也没有办法创建一个类的实例。controller 在项目中属于module,我们引入一个 controllers 的数组在 @Modules装饰器中,在例子中我们还没有定义其他的模块,所以我们现在可以在 AppModule中引入 `CatsController

@@filename(app.module)
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';

@Module({
  controllers: [CatsController],
})
export class AppModule {}
复制代码

Library-specific approach

之前我们讨论了使用 Nest 标准去操作响应数据,还有第二种方法去 操作响应数据就是 Library-specific approach,为了注入一个特定的请求对象,我们需要使用 @Res装饰器,为了展示不同我们按照如下方式重写了CatsController

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

@Controller('cats')
export class CatsController {
  @Post()
  create(@Res() res: Response) {
    res.status(HttpStatus.CREATED).send();
  }

  @Get()
  findAll(@Res() res: Response) {
     res.status(HttpStatus.OK).json([]);
  }
}
复制代码

上面的这种方式可以使用,但是使用的过程需要慎重,因为这种方法不太清晰,而且也的确有很多的问题。最主要的问题:代码会变得依赖平台而且不易于测试。此外,在上面的示例中,您将失去与依赖于Nest标准响应处理的Nest特性的兼容性,比如拦截器和@HttpCode() / @Header()装饰器。为了解决这个问题我们可以设置 passthroughtrue

@@filename()
@Get()
findAll(@Res({ passthrough: true }) res: Response) {
  res.status(HttpStatus.OK);
  return [];
}
复制代码

Porviders

概念:Providers 在Nest中是一个很基本的概念,许多基本的Nest类都可以被视为服务提供者、存储库、工厂、助手等等。Providers的主旨就是注入以来。这意味着对象之间可以创建各种关系,“连接”对象实例的功能可以在很大程度上委托给Nest运行时系统。
image.png

之前,我们构建了一个简单的CatsControllerController应该处理HTTP请求,并将更复杂的任务委托给ProvidersProviders是在模块中声明为Providers的普通JavaScript类。

Services

让我们从创建一个简单的 CatsService来开始学习,该服务将负责数据存储和检索。而且他设计用来服务于CatsController,因此,可以将其定义为Providers

@@filename(interfaces/cat.interface)
export interface Cat {
  name: string;
  age: number;
  breed: string;
}
复制代码
@@filename(cats.service)
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;
  }
}
复制代码

例子中的CatsService 是一个很基础的类,只有一个属性和两个方法。这里比较新的用法就是使用了@Injectable()装饰器,@Injectable()装饰器附加元数据,它声明CatsService是一个可以由Nest IoC容器管理的类。
下面我们在Controllers中使用一下这个Service

@@filename(cats.controller)
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 catsService: CatsService) {}

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

 @Get()
 async findAll(): Promise<Cat[]> {
   return this.catsService.findAll();
 }
}
复制代码

CatsService 通过类进行注册,注意私有语法的使用

Dependency injection

Nest是围绕通常被称为依赖注入的强大设计模式构建的。由于TypeScript的能力,管理依赖非常容易,因为只需要根据类型解析,Nest将通过创建并返回一个catsService实例来解析该catsService。

constructor(private catsService: CatsService) {}
复制代码

Scope

Providers一般和程序的生命周期是一致的,当应用程序启动时,必须解析每个依赖项,因此必须实例化每个Providers。相似的,当程序被注销的时候,每一个Providers也会被注销。但是,也有一些方法可以使Providers的生命周期限定在请求范围内。

Custom providers

Nest有一个内置的控制反转(“IoC”)容器,用来解决Providers之间的关系,这个特性是我们上面描述的依赖注入的基础,但是它的能力远比我们描述的强大。

Optional providers

有些时候,一些依赖我们没有必要一定去解析。比如说:你的类依赖一个配置对象。但是如果没有传递,则应该使用默认值,在这种情况下依赖变成了可选项,因为缺少这样一份依赖并不会导致什么错误。为了表明这个依赖是可选的,我们使用@Optional()来表明这个构造器是可选的。前面的例子显示了基于构造函数的注入,通过构造函数中的类指示依赖关系。

import { Injectable, Optional, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}
复制代码

Property-based injection

迄今为止我们所用的技术是基于构造器的注入,因为 providers的注入方式是通过 constructor的方式进行注入的,但是在很多的特殊案例中,property-based injection 的方法是更加有效的。比如说:如果你的顶级类依赖于一个或多个providers,通过在子类中从构造函数调用super()来传递它们是非常繁琐的,为了避免这个情况我们使用@Inject()装饰器在属性层级。

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}
复制代码

Provider registration

现在我们已经定一个了一个ProviderCatsService,并且我们也有一个自定义的service CatsController
,我们需要在Nest中注册这个service,这样它就可以进行注入,现在我们可以修改我们的module 文件,将服务添加到 @Module()装饰器的providers数组中。

@@filename(app.module)
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {}
复制代码

当面的目录应该是:

image.png

Modules

概念:模块是用@Module()装饰器注解的类。@Module()装饰器提供了Nest用来组织应用结构的元数据。

image.png

每一个应用至少要有一个 module(根模块),根模块是用来构建nest程序图的起点– Nest用来解析moduleprovider关系和依赖关系的内部数据结构。但是很少有程序真的就只拥有一个根模块,我们要推荐使用模块作为组织组件的有效方式。因此,对于大多数应用程序,最终的体系结构将采用多个模块,每个模块封装了一组密切相关的功能。

@Module()装饰器接受一个对象,它的属性描述了模块:

功能 描述
providers 这些提供商将被Nest注入器实例化,并且至少可以在这个模块中共享
controllers 这个模块中定义的一组必须被实例化的控制器
imports 导出此模块中所需的提供程序的导入模块列表
exports 由该模块提供的providers子集,应该在导入该模块的其他模块中可用

module 默认封装了 providers。这意味着不可能注入既不是当前模块的直接组成部分,也不可能从导入的模块导出的提供商。因此,我们可以将从模块导出的提供程序视为该模块的公共接口或API。

Feature modules

CatsController 和 CatsService属于同一个应用空间。因为他们是紧密关联的,所以现在有一个场景将他们划分到同功能模块,功能模块只是简单地组织与功能特性相关的代码,保持代码的组织并建立清晰的边界。这有助于我们管理复杂性并使用SOLID原则进行开发,特别是当应用程序和/或团队的规模增长时。

为了演示这个功能,我们在下面创建一个这样的module

@@filename(cats/cats.module)
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}
复制代码

如果要在CLI中创建模块,只需执行$ nest g module cats命令即可。

上面,我们在cats.module.ts文件中定义了CatsModule,并将所有与该模块相关的内容移到cats目录中。我们需要做的最后一件事是将这个模块导入到根模块中。

@@filename(app.module)
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule {}
复制代码

当前的文件目录:

image.png

Shared modules

在Nest中,模块默认是单例的,因此你可以在多个模块之间轻松地共享任何providers的同一个实例。

image.png

每个模块都自动成为一个共享模块, 一旦创建,它可以被任何模块重用。让我们设想一下,我们想要在几个模块之间分享 CatsService的实例,为了做这件事情我们需要先导出CatsService通过添加exports数组。

@@filename(cats.module)
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})
export class CatsModule {}

复制代码

现在,任何导入CatsModule的模块都可以访问CatsService,并将与所有导入它的模块共享相同的实例。

Module re-exporting

如上所示,模块可以导出它们的内部提供程序。此外,他们可以重新导出他们导入的模块。

@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class CoreModule {}
复制代码

Dependency injection

模块类也可以注入Provider。

@@filename(cats.module)
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {
  constructor(private catsService: CatsService) {}
}
复制代码

Global modules

如果你想在很多的module 都引入一写相同的module,这些按照之前的写法会比较繁琐。与Nest不同,Angular的Providers是在全局作用域中注册的。一旦注册了,在任何地方均可以使用。然而,Nest将Providers封装在模块范围内。如果不先导入封装的模块,你就不能在其他地方使用模块的Providers

当你想要任何地方都可以使用一些模块的时候,你可以使用@Global()装饰器。

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()装饰器让 模块拥有全局作用域的能力,全局模块应该只注册一次,通常由根模块或核心模块注册

所有东西都全球化并不是一个好的设计决策。全局模块可用来减少必要的样板的数量。导入数组通常是使模块的API对消费者可用的首选方式。

Dynamic modules

Nest模块系统包含一个强大的特性,称为动态模块,这个特性使您能够轻松地创建自定义的模块,可以动态地注册和配置`Providers`。
复制代码

下面是一个DatabaseModule动态模块定义的例子:

@@filename()
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,
    };
  }
}
复制代码

默认情况下,该模块定义了Connectionprovider,但是除此之外,依赖于使用entitiesoptions对象向forRoot方法传入数据,公开一些provider的集合,比如说 仓库。动态模块返回的属性扩展(而不是覆盖)了@Module()装饰器中定义的基本模块元数据。

如果你想在全局范围内注册一个动态模块,将global属性设置为true

{
    global: true,
    module: DatabaseModule,
    providers: providers,
    exports: providers,
}
复制代码

DatabaseModule可以通过以下方式导入和配置:

import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';

@Module({
    imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}

复制代码

如果你想反过来重新导出一个动态模块,你可以省略exports数组中的forRoot()方法调用:

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 AppModule {}
复制代码

Middleware

概念:中间键是一个方法,在路由处理之前调用。

参考应用文档

参考文档:nestjs.bootcss.com/

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享