2020-04-15:
笔记:nestjs学习
官方介绍:
Nest 是一个用于构建高效,可扩展的 Node.js 服务器端应用程序的框架。它使用渐进式 JavaScript,内置并完全支持 TypeScript(但仍然允许开发人员使用纯 JavaScript 编写代码)并结合了 OOP(面向对象编程),FP(函数式编程)和 FRP(函数式响应编程)的元素。
在底层,Nest使用强大的 HTTP Server 框架,如 Express(默认)和 Fastify。Nest 在这些框架之上提供了一定程度的抽象,同时也将其 API 直接暴露给开发人员。这样可以轻松使用每个平台的无数第三方模块。
一、安装、创建和启动项目:
$ npm i -g @nestjs/cli
$ nest new project-name
项目结构:入口(使用 NestFactory
用来创建 Nest 应用实例)、控制器、根模块、服务
main.ts:
app.module.ts:
使用依赖注入的方式加入控制器和服务.
app.controller.ts:
管理路由的地方。控制器负责处理传入的 请求 和向客户端返回 响应 。
可以通过CLI单独创建控制器: $ nest g controller cats
app.service.ts:
服务类,放置各种服务方法。
启动项目:(提供了以下几种方法)
二、控制器:
为了创建一个基本的控制器,我们必须使用装饰器
。装饰器将类与所需的元数据关联,并使 Nest
能够创建路由映射(将请求绑定到相应的控制器)。
1、路由 :
@Controller('cats') export class CatsController { @Get('food') findAll(): string { return 'This action returns all cats food'; } }
@Controller('cats'):控制器装饰器提供路径前缀
@Get('food') : 请求方法装饰器声明请求方法,并且声称路由映射 GET /cats/food 。Nest
以相同的方式提供其余的端点装饰器- @Put()
、 @Delete()
、 @Patch()
、 @Options()
、 @Head()
和 @All()
。这些表示各自的 HTTP
请求方法。
2、Request:
把客户端的请求细节(请求对象)通过装饰器@Req 注入处理程序:
@Res()
只是 @Response()
的别名。
注意⚠️:Nest
检测处理程序是否正在使用 @Res()
或 @Next()
,如果两个方法都用了的话, 那么在这里的标准方式就是自动禁用此路由, 你将不会得到你想要的结果。
import { Request } from 'express';
@Controller('cats') export class CatsController { @Get() findAll(@Req() request: Request): string { return 'This action returns all cats'; } }
3、路由通配符:
路由支持re:
@Get('ab*cd') findAll() { return 'This route uses a wildcard'; } // 能匹配到abcd, ab_cd, abecd
4、状态码:
默认成功为200,除了POST请求返回(201),这个行为可以通过@HttpCode()更改:
@Post() @HttpCode(204) create() { return 'This action adds a new cat'; }
5、Header:
自定义响应头:
impost {Header} from '@nestjs/common'; @Post() @Header('Cache-Control', 'none') create() { return 'This action adds a new cat'; }
6、重定向:
@Redirect()
带有必需的 url
参数和可选的 statusCode
参数。 如果省略,则 statusCode
默认为 302
。这个重定向到内容可以动态定义:
@Get('docs') @Redirect('https://docs.nestjs.com', 302) getDocs(@Query('version') version) { if (version && version === '5') { return { url: 'https://docs.nestjs.com/v5/' }; } }
7、路由参数:
@Get(':id') findOne(@Param() params): string { console.log(params.id); return `This action returns a #${params.id} cat`; }
也可以指定参数:
@Get(':id') findOne(@Param('id') id): string { return `This action returns a #${id} cat`; }
8、范围:
Node.js
不遵循请求/响应多线程无状态模型,每个请求都由主线程处理。因此,使用单例实例对我们的应用程序来说是完全安全的。
9、请求负载:
定义POST请求的数据结构需要用类定义DTO(数据传输对象模式)。由于 TypeScript
接口在转换过程中被删除,所以 Nest
不能在运行时引用它们。这一点很重要,因为诸如管道之类的特性在运行时能够访问变量的元类型时提供更多的可能性。
// dot.ts: export class CreateCatDto { readonly name: string; readonly age: number; readonly breed: string; } // controller.ts: @Post() async create(@Body() createCatDto: CreateCatDto) { return 'This action adds a new cat'; }
三、Provider:
发挥DI的地方:通过 constructor
注入依赖关系, 这意味着对象可以彼此创建各种关系,并且“连接”对象实例的功能在很大程度上可以委托给 Nest
运行时系统。
控制器应处理 HTTP
请求并将更复杂的任务委托给 providers。Providers 是纯粹的 JavaScript
类,在其类声明之前带有 @Injectable()
装饰器。
1、提供服务:
需要定义接口
// cat.interface export interface Cat { name: string; age: number; breed: string; } // cat.service.ts 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; } }
四、Module:
模块封装Provider。这意味着如果Provider不是当前模块的一部分, 也不是从另外模块(已导入)导出的,那么它就是无法注入的。
1、子模块在根模块中注册:
import { Module } from '@nestjs/common'; import { CatsModule } from './cats/cats.module'; @Module({ imports: [CatsModule], }) export class ApplicationModule {}
2、共享模块:
在 Nest 中,默认情况下,模块是单例,因此您可以轻松地在多个模块之间共享同一个Provider实例。
需要把 这个Provider 放到 exports
数组中:
// 现在,每个导入 CatsModule 的模块都可以访问 CatsService ,并且它们将共享相同的 CatsService 实例 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 {}
3、Provider注入到模块中:
多用于配置型Provider
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 readonly catsService: CatsService) {} }
4、全局模块:
一次注册,全局使用。
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
装饰器使模块成为全局作用域。 全局模块应该只注册一次,最好由根或核心模块注册。 在上面的例子中,CatsService
组件将无处不在,而想要使用 CatsService
的模块则不需要在 imports
数组中导入 CatsModule
。
5、动态模块:
实际上,动态模块扩展了模块元数据。当您需要动态注册组件时,这个实质特性非常有用。
例如:动态传入provider给database模块
// './database/database.module' 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, }; } } // root module 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 ApplicationModule {}
五、中间件:
中间件是在路由处理程序 之前 调用的函数。 中间件函数可以访问请求和响应对象,以及应用程序请求响应周期中的 next()
中间件函数。 next()
中间件函数通常由名为 next
的变量表示。
1、实现一个中间件:
必须在函数中或具有@Injectable() 的类中实现
// logger.middleware.ts import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: Function) { console.log('Request...'); next(); } }
2、应用中间件:
必须使用模块类的 configure()
方法来设置它们。包含中间件的模块必须实现 NestModule
接口。
可以使用 async/await
来实现 configure()
方法的异步化(例如,可以在 configure()
方法体中等待异步操作的完成)。
例如logger中间件在根模块上使用:
// app.module.ts import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { CatsModule } from './cats/cats.module'; @Module({ imports: [CatsModule], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) // 引入logger中间件 .forRoutes({ path: 'cats', method: RequestMethod.GET }); // 限制使用的路由及请求方法 } }
3、关于帮助类MiddlewareConsumer
:
它提供了几种内置方法来管理中间件。他们都可以被简单地链接起来。
1、forRoutes()
可接受一个字符串、多个字符串、对象、一个控制器类甚至多个控制器类。在大多数情况下,您可能只会传递一个由逗号分隔的控制器列表。限制能使用这个中间件的路由或控制器。
2、exclude()方法轻松地排除某些路由。该方法不适用于函数中间件(在函数中而不是在类中定义的中间件)。此方法不排除来自更通用路由(例如,通配符)的路径。如果您需要这种级别的控制,您应该将路径限制逻辑直接放入中间件
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST }
)
.forRoutes(CatsController);
4、函数式中间件:
适用于如loggerMiddleware这类没有成员,没有额外的方法,没有依赖关系的中间件
export function logger(req, res, next) {
console.log(`Request...`);
next();
};
5、全局中间件:
一次性将中间件绑定到每个注册路由。在main.ts中加入app.use(logger);
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);