概念
创建项目
npm i -g @nestjs/cli
nest new project-name
复制代码
默认创建了如下的目录
目录代表意义
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
处理哪个请求,通常一个控制器有多个路由,不同的路有可以执行不同的动作。
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 的前面,告诉nest
为HTTP
请求创建一个图书的端点,这个端点相当于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()
装饰器。为了解决这个问题我们可以设置 passthrough
为true
。
@@filename()
@Get()
findAll(@Res({ passthrough: true }) res: Response) {
res.status(HttpStatus.OK);
return [];
}
复制代码
Porviders
概念:Providers
在Nest中是一个很基本的概念,许多基本的Nest类都可以被视为服务提供者、存储库、工厂、助手等等。Providers
的主旨就是注入以来。这意味着对象之间可以创建各种关系,“连接”对象实例的功能可以在很大程度上委托给Nest运行时系统。
之前,我们构建了一个简单的CatsController
。Controller
应该处理HTTP
请求,并将更复杂的任务委托给Providers
。Providers
是在模块中声明为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 {}
复制代码
当面的目录应该是:
Modules
概念:模块是用@Module()
装饰器注解的类。@Module()
装饰器提供了Nest用来组织应用结构的元数据。
每一个应用至少要有一个 module
(根模块),根模块是用来构建nest程序图的起点– Nest用来解析module
和provider
关系和依赖关系的内部数据结构。但是很少有程序真的就只拥有一个根模块,我们要推荐使用模块作为组织组件的有效方式。因此,对于大多数应用程序,最终的体系结构将采用多个模块,每个模块封装了一组密切相关的功能。
@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 {}
复制代码
当前的文件目录:
Shared modules
在Nest中,模块默认是单例的,因此你可以在多个模块之间轻松地共享任何providers
的同一个实例。
每个模块都自动成为一个共享模块, 一旦创建,它可以被任何模块重用。让我们设想一下,我们想要在几个模块之间分享 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,
};
}
}
复制代码
默认情况下,该模块定义了Connection
provider,但是除此之外,依赖于使用entities
和options
对象向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/